Skip to main content

Terraform) Azure Bastion Host 구축

Create Azure Virtual Machine Use Terraform Terraform으로 기본적인 IaaS 구축을 진행합니다.

Kyungmin Park

Create Azure Virtual Machine Use Terraform

Terraform으로 기본적인 IaaS 구축을 진행합니다. (모듈 사용 X)


테스트 환경

  • PC: Apple MacBook Air 2020(M1)
  • OS: macOS Monterey 12.3.1
  • IDE: Visual Studio Code 1.66.1
  • Terraform: v1.1.8 (on darwin_arm64), brew install
  • Azure CLI: stable 2.35.0 (Microsoft Azure CLI 2.0)

전체 구성도

Image

Azure VM 접속 방식

  • Password로 접근하는 방식
  • SSH Key를 이용하여 접근하는 방식
    2가지가 있고 이 글에서는 SSH Key를 이용하여 접근하는 방식을 사용합니다.


ssh-keygen을 이용하여 키를 생성하는 방법은 아래를 참고하시면됩니다.

$ ssh-keygen
# WorkSpace 디렉터리 지정
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/user/.ssh/id_rsa): /WorkSpace/KP/testvm-public
# 비워 두시면 됩니다.
Enter passphrase (empty for no passphrase): 
Your identification has been saved in /WorkSpace/KP/testvm-public
Your public key has been saved in /WorkSpace/KP/testvm-public.pub
The key fingerprint is:
SHA256:Oc/6iqU5MI1JkOjuws3WRupObvo0L/lBEYLD5kwAu/o
The key's randomart image is:
+---[RSA 3072]----+
|*..o .           |
|.Bo . .          |
|B .. .           |
| =  . .  .       |
|o  . =  S        |
|..  *..  +       |
|+ o+=+  . o      |
|.++Oooo= .       |
|..E=+o+.oo.      |
+----[SHA256]-----+

위와 같은 방법을 사용하여 Bastion Host 와 Private VM을 위한 Key를 각각 생성해 주시면됩니다.

파일이름

  • Bastion Host: testvm-public
  • Private VM: testvm-private

Azure Cli 로그인

Azure 인프라를 Terraform으로 컨트롤 하려면 Azure에 로그인을 하셔야합니다.
방법은 간단합니다.

$ az login

웹사이트로 이동하고 로그인하면 cli도 로그인됩니다.
정상적으로 로그인 되었는지 확인해봅니다.

$ az account show -o json

계정과 테넌트가 정상적으로 확인되면 끝입니다.


전체 파일

  • main.tf
  • Provider 선언
  • Resource Group 확인 및 생성
  • VNet 생성
  • Subnet 생성
  • NAT Gateway 생성
  • Resource Association
  • data.tf
  • Resource Group 존재여부 체크를 위한 데이터
  • output.tf
  • Resource Group가 기존에 있었는지 확인
  • NAT Gateway Public IP 확인
  • Bastion Host Public IP 확인
  • sg.tf
  • SSH 허용을 Security Group으로 정의(단일 리소스가 내용이 길어서 별도로 빼놨습니다.)
  • test.tf
  • Bastion Host 및 Private Virtual Machine 생성 정의(NIC, SSH Key 등)

Terraform Apply

코드는 제가 GitHub에 올린 파일입니다.

- main.tf

# Define Provider
terraform {
    required_providers {
        azurerm = {
            source  = "hashicorp/azurerm"
            version = "=3.2.0"
        }
    }
}

provider "azurerm" {
    features {}
}


### Define & Creation ###

# Define Resource Group
resource "azurerm_resource_group" "resource-group" {
    count                           = data.azurerm_resource_group.resource-group.name == var.resource-group-name ? 0 : 1
    name                            = var.resource-group-name
    location                        = var.azure-location
}

# Create vnet(Virtual Network)
resource "azurerm_virtual_network" "vnet" {
    name                            = var.vnet-name
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location
    address_space                   = [var.vnet-cidr]
    tags                            = var.tagging
}

resource "azurerm_subnet" "public-subnet" {
    name                            = var.public-subnet-name
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    virtual_network_name            = azurerm_virtual_network.vnet.name
    address_prefixes                = [var.subnet-cidrs["public"]]
}

resource "azurerm_subnet" "private-subnet" {
    name                            = var.private-subnet-name
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    virtual_network_name            = azurerm_virtual_network.vnet.name
    address_prefixes                = [var.subnet-cidrs["private"]]
}

resource "azurerm_public_ip" "nat-gw-public-ip" {
    name                            = var.public-nat-ip-name
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location
    allocation_method               = var.nat-gateway-allocation-method
    sku                             = var.nat-gateway-public-sku
    tags                            = var.tagging
}

resource "azurerm_nat_gateway" "nat-gw" {
    name                            = var.public-nat-name
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location
    tags                            = var.tagging
}


### Association ###

# NAT - Subnet
resource "azurerm_subnet_nat_gateway_association" "public-nat-association" {
    subnet_id                       = azurerm_subnet.private-subnet.id
    nat_gateway_id                  = azurerm_nat_gateway.nat-gw.id
}

# NAT - Public IP
resource "azurerm_nat_gateway_public_ip_association" "nat-public-ip-association" {
    nat_gateway_id                  = azurerm_nat_gateway.nat-gw.id
    public_ip_address_id            = azurerm_public_ip.nat-gw-public-ip.id
}

# Subnet - Security Group
resource "azurerm_subnet_network_security_group_association" "public-security-group-association" {
    subnet_id                       = azurerm_subnet.public-subnet.id
    network_security_group_id       = azurerm_network_security_group.public-subnet-security-group.id
}

- data.tf

data "azurerm_resource_group" "resource-group" {
    name = var.resource-group-name
}

- output.tf

output "check-resource-group" {
    value = data.azurerm_resource_group.resource-group.name == var.resource-group-name ? format("%s was Exist", var.resource-group-name) : format("%s was Not Exist", var.resource-group-name)
}

output "nat-gateway-public-ip" {
    value = azurerm_public_ip.nat-gw-public-ip.ip_address
}

output "public-test-vm-public-ip" {
    value = azurerm_public_ip.public-vm-nic-public-ip.ip_address
}

- sg.tf

resource "azurerm_network_security_group" "public-subnet-security-group" {
    name                            = var.public-subnet-sg-name
    location                        = data.azurerm_resource_group.resource-group.location
    resource_group_name             = data.azurerm_resource_group.resource-group.name

    security_rule = [ {
      access                                        = "Allow"
      description                                   = "Allow SSH Inbound"
      destination_address_prefix                    = "*"
      destination_address_prefixes                  = null
      destination_application_security_group_ids    = null
      destination_port_range                        = "22"
      destination_port_ranges                       = null
      direction                                     = "Inbound"
      name                                          = "Allow SSH"
      priority                                      = 100
      protocol                                      = "*"
      source_address_prefix                         = "*"
      source_address_prefixes                       = null
      source_application_security_group_ids         = null
      source_port_range                             = "*"
      source_port_ranges                            = null
    }]
    tags                                            = var.tagging
}

- test.tf

# Virtual Machine Test for Network

# Public Subnet
resource "azurerm_public_ip" "public-vm-nic-public-ip" {
    name                            = "MSA-TEST-VM-PUBLIC-NIC-PUBLIC-IP"
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location
    allocation_method               = "Static"
    sku                             = "Standard"

    tags                            = var.tagging
}

resource "azurerm_network_interface" "public-vm-nic" {
    name                            = "MSA-TEST-VM-PUBLIC-NIC"
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location

    ip_configuration {
      name                          = "nic-configure"
      subnet_id                     = azurerm_subnet.public-subnet.id
      private_ip_address_allocation = "Static"
      private_ip_address            = "192.168.0.10"
      public_ip_address_id          = azurerm_public_ip.public-vm-nic-public-ip.id
    }
    tags                            = var.tagging
}

resource "azurerm_ssh_public_key" "public-test-vm-public-key" {
    name                            = "MSA-TEST-VM-PUBLIC-KEY"
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location
    public_key                      = file("../KP/testvm-public.pub")
}

resource "azurerm_linux_virtual_machine" "public-vm" {
    name                            = "MSA-TEST-VM-PUBLIC"  
    location                        = data.azurerm_resource_group.resource-group.location
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    size                            = "Standard_B2s"
    network_interface_ids           = [azurerm_network_interface.public-vm-nic.id]
    admin_username                  = "vmadmin"
    disable_password_authentication = true

    admin_ssh_key {
        username                    = "vmadmin"
        public_key                  = azurerm_ssh_public_key.public-test-vm-public-key.public_key
    }
    source_image_reference {
        publisher                   = "Canonical"
        offer                       = "UbuntuServer"
        sku                         = "18.04-LTS"
        version                     = "latest"
    }
    os_disk {
        name                        = "MSA-TEST-VM-PUBLIC-OS-DISK"
        caching                     = "None"
        storage_account_type        = "StandardSSD_LRS"
    }
    tags                            = var.tagging
}

# Private Subnet
resource "azurerm_network_interface" "private-vm-nic" {
    name                            = "MSA-TEST-VM-PRIVATE-NIC"
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location

    ip_configuration {
      name                          = "nic-configure"
      subnet_id                     = azurerm_subnet.private-subnet.id
      private_ip_address_allocation = "Static"
      private_ip_address            = "192.168.10.10"
    }
    tags                            = var.tagging
}

resource "azurerm_ssh_public_key" "private-test-vm-public-key" {
    name                            = "MSA-TEST-VM-PRIVATE-KEY"
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    location                        = data.azurerm_resource_group.resource-group.location
    public_key                      = file("../KP/testvm-private.pub")
}

resource "azurerm_linux_virtual_machine" "private-vm" {
    name                            = "MSA-TEST-VM-PRIVATE"  
    location                        = data.azurerm_resource_group.resource-group.location
    resource_group_name             = data.azurerm_resource_group.resource-group.name
    size                            = "Standard_B2s"
    network_interface_ids           = [azurerm_network_interface.private-vm-nic.id]
    admin_username                  = "vmadmin"
    disable_password_authentication = true

    admin_ssh_key {
        username                    = "vmadmin"
        public_key                  = azurerm_ssh_public_key.private-test-vm-public-key.public_key
    }
    source_image_reference {
        publisher                   = "Canonical"
        offer                       = "UbuntuServer"
        sku                         = "18.04-LTS"
        version                     = "latest"
    }
    os_disk {
        name                        = "MSA-TEST-VM-PRIVATE-OS-DISK"
        caching                     = "None"
        storage_account_type        = "StandardSSD_LRS"
    }
    tags                            = var.tagging
}

- variable.tf

variable "resource-group-name" {
    type                        = string
    default                     = "Cloud_MSA-Dev-Test"
}

variable "azure-location" {
    type                        = string
    default                     = "Korea Central"
}

variable "vnet-name" {
    type                        = string
    # 이름은 2~64자 사이여야 합니다.
    # 이름은 영문, 숫자, 밑줄, 마침표 또는 하이픈만 포함할 수 있습니다. 단 영문 또는 숫자로 시작하고 영문, 숫자 또는 밑줄로 끝나야 합니다.
    # 값은 비어 있으면 안 됩니다.
    default                     = "MSA-vnet"
}

variable "vnet-cidr" {
    type                        = string
    default                     = "192.168.0.0/16"
}

variable "internet-cidr" {
    type                        = string
    default                     = "0.0.0.0/0"
}

variable "public-subnet-name" {
    type                        = string
    # 이름은 영문, 숫자, 밑줄, 마침표 또는 하이픈만 포함할 수 있습니다. 단 영문 또는 숫자로 시작하고 영문, 숫자 또는 밑줄로 끝나야 합니다.
    default                     = "public-subnet"
}

variable "public-subnet-sg-name" {
    type                        = string
    default                     = "MSA-public-subnet-security-group"
}

variable "private-subnet-name" {
    type                        = string
    default                     = "private-subnet"
}

variable "subnet-cidrs" {
    type                        = map(string)
    default                     = {
        "public"  = "192.168.0.0/24"
        "private" = "192.168.10.0/24"
        }
}

variable "public-nat-name" {
    type                        = string
    default                     = "MSA-ngw"
}

variable "public-nat-ip-name" {
    type                        = string
    default                     = "MSA-nat-gw-public-ip"
}

variable "nat-gateway-allocation-method" {
    type                        = string
    default                     = "Static"
}

variable "nat-gateway-public-sku" {
    type                        = string
    default                     = "Standard"
}

variable "tagging" {
    type                        = map(string)
    default                     = {
        "Create by" = "eocis"
        "terraform" = "true"
    }
}

참고사항

GitHub

Azure VM은 인터넷 통신을 하기위해 별도로 Route Table을 설정할 필요가 없습니다.
기본적으로 Outbound 통신이 가능합니다. (링크)
하지만 NAT Gateway를 두고 통신하는 것이 좋습니다.
AWS와는 설정하는게 달라 좀 까다롭긴했네요

또 Liunx Virtual Machine에서는 traceroute와 ping명령어가 정상적으로 동작하지 않습니다.
네트워크 테스트는 curl로 진행하시면 될 듯 하네요

  • 로컬에 Docker nginx이미지를 가동하고 Azure VM에서 로컬 nginx container로 curl요청을 보냅니다.
    확인은 docker logs -n 10 (Container ID) 로 확인하시면 됩니다.
$ sudo docker logs -n 10 cf4 
58.150.221.203 - - [19/Apr/2022:08:50:08 +0000] "POST ~~~~~~~~~