구름

[테라폼] AWS, Azure, GCP 멀티 클라우드 본문

Code/IaC

[테라폼] AWS, Azure, GCP 멀티 클라우드

Cloudest 2021. 2. 10. 13:24
 

Cloudest - 블로그 이사했습니다

노션으로 블로그를 옮겼습니다.

흥미로운 포스팅이 올라옵니다!

cloudest.oopy.io

🛠
준비물 :
1. 자신 소유의 도메인
2. AWS, Azure, GCP 계정
3. 테라폼 사용가능한 WSL2 (EC2같은 다른 리눅스도 가능하다. Ubuntu 18.04 or 20.04 추천)

멀티 클라우드

  • 멀티 클라우드는 2개 이상의 클라우드 플랫폼에서 하나의 서비스를 운영하는 것
  • 한가지 플랫폼에 Lock-In 되는 것을 방지할 수 있다.
  • 여러 플랫폼의 경쟁력있는 서비스를 골라서 사용할 수 있다.
  • 정말 만약의 사태에 한쪽 클라우드 플랫폼 전체적인 장애 발생시 더 높은 가용성을 제공할 수 있다.

시작 전

  • AWS는 많이 사용해보았지만 Azure와 Gcp는 이번이 처음 사용이다.
  • 개인 도메인이 없다면 Freenom [프리놈]과같은 사이트 통해 무료로 도메인을 대여할 수 있다고 한다.
  • GCP 회원가입 안 넘어가질 때
    • 다시 콘솔에서 무료로 사용하기를 누르면 약관동의를 체크하고 넘어갈 수 있다.
  • 시행착오 : 이번 실습을 진행하면서 겪은 시행착오와 해결방법등을 적어두었다. 본문 내용들은 해결이 완료된 후 수정된 내용들이다.
    💡
    변수를 제대로 인식하지 못하는 경우가 있어서 실습의 완성을 위해 변수대신에 직접 값을 입력한 부분들이 종종 있다.
    • 1. Azure CLI 환경 구성 - 해결
      • 아래 코드 실행
        apt update
        apt install azure-cli
      • WSL2에선 az login명령이 안된다. → az login --device-codehttps://microsoft.com/devicelogin 으로 접속해서 명령 결과로 나온 코드 입력하면 로긴된다.
        로그인 결과
        [
          {
            "cloudName": "AzureCloud",
            "id": "a573addd-xxxx-xxxx-xxxx-xxxxdataxxxx",
            "isDefault": true,
            "name": "무료 체험",
            "state": "Enabled",
            "tenantId": "4c6c5d14-xxxx-xxxx-xxxx-xxxxdataxxxx",
            "user": {
              "name": "wwdcr2@naver.com",
              "type": "user"
            }
          }
        ]
      • az account get-access-token명령으로 토큰 확인
        
        root@jwjung:/web_infra# az account get-access-token
        {
          "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC80YzZjNWQxNC1kN2YyLTRjZjgtOWU0Zi0zY2MwYmNjMjlmNmIvIiwiaWF0IjoxNjEyMjU1NTE4LCJuYmYiOjE2MTIyNTU1MTgsImV4cCI6MTYxMjI1OTQxOCwiYWNyIjoiMSIsImFpbyI6IkFVUUF1LzhUQUFBQUo3c3JyaGhYdDFReWVkV05EYUJid0xZdWJwdytBMUlJTUo3NE55bDhvNU91Z0FXandjcjA1YnNlOTR2SGwxck5qM21tM3o2ajlUT3BSMTNEL0ZLeHdnPT0iLCJhbHRzZWNpZCI6IjE6bGl2ZS5jb206MDAwMzAwMDAwQ0JGNzI0NCIsImFtciI6WyJwd2QiXSwiYXBwaWQiOiIwNGIwNzc5NS04ZGRiLTQ2MWEtYmJlZS0wMmY5ZTFiZjdiNDYiLCJhcHBpZGFjciI6IjAiLCJlbWFpbCI6Ind3ZGNyMkBuYXZlci5jb20iLCJmYW1pbHlfbmFtZSI6Ikppd29uIiwiZ2l2ZW5fbmFtZSI6Ikp1bmciLCJncm91cHMiOlsiNjFmMGMxZTYtY2Q5ZS00MTdkLThlMGUtNzViOTIyMjg1N2E5Il0sImlkcCI6ImxpdmUuY29tIiwiaXBhZGRyIjoiMTIxLjEzMy4zNS4yMDQiLCJuYW1lIjoiSml3b25KdW5nIiwib2lkIjoiMWUxY2YwNDgtNTUxMi00MDNkLWJjYjItOTBhOWY0NDRmOTc5IiwicHVpZCI6IjEwMDMyMDAwRjM4RTQ4QTEiLCJyaCI6IjAuQUFBQUZGMXNUUExYLUV5ZVR6ekF2TUtmYTVWM3NBVGJqUnBHdS00Qy1lR19lMFp4QUxjLiIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6ImFDak90eHE5MmN6ZnB3c29IQnlkbEtGTXllSGdKaG5XZjZabHFFXzdGYUUiLCJ0aWQiOiI0YzZjNWQxNC1kN2YyLTRjZjgtOWU0Zi0zY2MwYmNjMjlmNmIiLCJ1bmlxdWVfbmFtZSI6ImxpdmUuY29tI3d3ZGNyMkBuYXZlci5jb20iLCJ1dGkiOiJ1V29KeHB3Mm9VZVlya3RUemxBYUFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyI2MmU5MDM5NC02OWY1LTQyMzctOTE5MC0wMTIxNzcxNDVlMTAiLCJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX3RjZHQiOjE2MDQ0MjExNDZ9.aoLFCYI0SInSMNiiYqWMs8xDwCySYyVBs0nrEaceaXSc8WICqeMmzAe_Kg_CS1Me3PQKaIzYphP08_kHgQj07sf4cVyO6mP4FJ9n10ft50prgQEOysEXtHZKBSeX-KlM3UEZtAh-EpVywLx7EUb9E0Qp3VJPHpuHiR7yhtv1sh_kwEIDAWsozvmVcXeMvB4wTPJgGBWsF-uqxlbsVLnsNFBgOBJq-yqweGMs9EvQL9BQS2ee1onCVDmE4IGtPsDXRlCBmtTMGvVx1Z-reEM_Vxk1P5Ig2r_D3zIX269scL6ffHOrThaza_dSMT5_LzN1n_WfASO1FKIMhJZkJiN8DA",
          "expiresOn": "2021-02-02 18:50:14.764144",
          "subscription": "a573addd-d0a0-4748-a03b-81c5b067d960",
          "tenant": "4c6c5d14-d7f2-4cf8-9e4f-3cc0bcc29f6b",
          "tokenType": "Bearer"
        }

       

    • 2. terraform apply 결과 Error - 해결
      • apply 에러 내용 + Cloudflare를 통해 Azure만 접속되는 상황
        AWS
        ---
        Error: Error associating EIP: Gateway.NotAttached: Network vpc-078cf7dd35449156f is not attached to any internet gateway
                status code: 400, request id: edab0fd5-9836-4082-9d1c-2476323bbc91
        
        Error: error creating route table: InvalidVpcID.NotFound: The vpc ID 'aws_vpc.aws-vpc.id' does not exist
                status code: 400, request id: 53a2ff53-3c40-451a-9fc1-a78dc7647dd4
        
        Error: error attaching EC2 Internet Gateway (igw-0cdd1a757913153fb): Error attaching internet gateway: 
        InvalidVpcID.NotFound: The vpc ID 'aws_vpc.aws-vpc.id' does not exist
                status code: 400, request id: 2f8aa1dd-38f6-47c4-b8bd-2cd8f0970f51
        
        GCP
        ---
        Error: Error creating Address: googleapi: Error 403: Compute Engine API has not been used in project 291777465078 
        before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/compute.googleapis.com/overview?project=291777465078 then retry.
         If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry., accessNotConfigured
        
        Error: Error creating Network: googleapi: Error 403: Compute Engine API has not been used in project 291777465078 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/compute.googleapis.com/overview?project=291777465078 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry., accessNotConfigured
        
        
        
      • AWS : aws_vpc.aws-vpc.id 변수값에 " " 빼주기 (변수를 문자로인식)
      • GCP : 3번에서 해결
    • 3. GCP Compute Engine API 문제 - 해결
      • https://console.developers.google.com/apis/api/compute.googleapis.com/overview?project=[프로젝트넘버] 사이트로 이동
      • Compute Engine API를 사용하기 → 결제를 허용한 계정이 필요하다.
      • 결제 계정 설정 및 Compute Engine API 설치 완료된 후 화면 → 개요 옆에 API 사용 중지로 언제든 API를 차단할 수 있다.
    • 4. GCP로 만든 인스턴스의 UserData가 입력되지 않는 문제 - 해결
      • 생성된 인스턴스의 IP로 웹서버 접속불가
      • SSH 접속을 허용하고 들어갔을 때 apache2가 설치되지 않음 = UserData 적용 안됨
      • gcp-main.tf파일의 코드 변경
         #metadata_startup_script = data.template_file.metadata_startup_script.rendered  # 이코드를 아래와 같이 변경
          metadata = {
            startup-script = <<SCRIPT
                sudo apt-get update
                sudo apt update 
                sudo apt install -y apache2 
                sudo systemctl start apache2 
                sudo systemctl enable apache2 
                echo "<h1> MultiCloud Demo Google Cloud </ h1>"| sudo tee /var/www/html/index.html
                SCRIPT
          }
    • 4. 로드밸런싱 문제 - 해결
      • 프로비저닝 완료 후 웹서버에 접속하면 1개의 서버에만 연결된다.
      • 브라우저의 private모드, 기록삭제, 다른브라우저사용 등 다양한 시도를 해봤지만 1개의 서버에만 연결
      • 하루종일 찾아보다가 오레곤에 생성한 인스턴스를 시작으로 핸드폰, 다른IP의 PC등 여러환경에서 시도해본 결과 로드밸런싱이 정상적으로 작동한다. 하지만 엔드디바이스의 접속정보를 가지고있는지 한서버에서는 한쪽으로만 라우팅이 되었다.
      • 무료버전은 failover를 제공하지 않는다.
  • 간단한 멀티클라우드 아키텍처 - 사용자는 도메인에 접속하면 Cloudflare가 각 플랫폼의 인스턴스로 연결 해 준다.

Terraform 환경


  • 작업 디렉터리 : WSL2 내부의 /web_infra/.
  • Terraform 버전 : v0.13.0 (0.14.5 버전은 실습 에러남) → terraform 0.13upgrade .명령으로 업그레이드
  • Terraform 버전 변경 : tfswitch 프로그램 사용
    • 버전 변경 안될 때
      • 혹시나 실행이 잘 안되는 경우 upgrade v0.13.0v0.14.5 버전을 변경해가면서 사용해보자
      • 아래와 같이 tfswitc로 v0.13.0 다운그레이드가 안되는경우 terraform.tfstate 파일을 삭제하자
      • rm /web_infra/terraform.tfstate*명령으로 삭제 하면 버전변경 가능!

Cloudflare 가입 및 도메인 추가

  • 2. 구매한 도메인의 NS 변경 (가비아 기준)
    • 가입 후 다음과 같은 창이 뜨면 뒤로가기를 누른다.
    • 2021.02.02 기준 이런 창이 나왔다. 이 이름서버에 맞게 가비아 네임서버 변경해주기
    • 변경된 가비아 네임서버정보
    • 변경 후 파란박스가 있는 위치에서 변경 확인? 눌러주기
  • 3. 도메인 - Cloudflare 연결 (레코드 추가)
    • DNS 탭으로 이동
    • 연결 완료 메일을 통한 활성화 완료
  • 4. Page Rule 설정을 통한 실제 페이지에 연결
    • Page Rule 탭으로 이동
    • Cloudflare는 기본 3개의 Page Rules를 무료로 제공한다.
    • 내 블로그에 연결하는 Page Rule 생성 후 저장
    • Page Rule은 언제든 On/Off와 삭제 및 수정이 가능하다.
  • 5. API Keys - Cloudflare
    • cloudflare Overvier탭의 API항목으로 이동
    • 이메일 인증
    • API Tokens탭으로 이동후 두개의 Key값을 View를 통해 확인하고 메모해둔다.

AWS에 WEB배포 (Terraform)

  • 1. 공통 변수에 대한 파일 생성 (common-variables.tf)
    • common-variables.tf 파일에 아래 내용 추가
      #Define application name
      variable "app_name" {
        type = string
        description = "Application name"
        default = "kopicloud"
      }
      #Define application environment
      variable "app_environment" {
        type = string
        description = "Application environment"
        default = "demo"
      }
  • 2. AWS 변수 선언 및 설정값 추가 (aws-variables.tf)
    • aws-variables.tf 파일에 아래 내용 추가
      #AWS authentication variables
      variable "aws_access_key" {
        type = string
        description = "AWS Access Key"
      }
      variable "aws_secret_key" {
        type = string
        description = "AWS Secret Key"
      }
      variable "aws_key_pair" {
        type = string
        description = "AWS Key Pair"
      }
      #AWS Region
      variable "aws_region" {
        type = string
        description = "AWS Region for the VPC"
        default = "ap-northeast-2"
      }
      #AWS AZ
      variable "aws_az" {
        type = string
        description = "AWS AZ"
        default = "ap-northeast-2c"
      }
      #VPC CIDR
      variable "aws_vpc_cidr" {
        type = string
        description = "CIDR for the VPC"
        default = "10.1.0.0/16"
      }
      #Subnet CIDR
      variable "aws_subnet_cidr" {
        type = string
        description = "CIDR for the subnet"
        default = "10.1.1.0/24"
      }
  • 3. AWS 인증정보 파일 생성 (terraform.tfvars)
    • terraform.tfvars파일에 아래 내용 추가
      #AWS authentication variables / 미리 생성한 액세스키, 비밀키, Key-pair이름 입력
      aws_access_key = "[액세스키]" 
      aws_secret_key = "[비밀키]"
      aws_key_pair = "[키페어 이름]" #SSH 접속을 하지 않기 때문에 생성한 Key-pair 이름만 알면 된다.
  • 4. EC2에 Apache 설치 및 index.html 구성을 위한 파일 생성 (aws-user-data.sh)
    • 이 작업은 부트스트랩 파일을 통한 작업이다.
    • aws-user-data.sh 파일에 아래 내용 추가
      #! /bin/bash
      sudo apt-get update
      sudo apt-get install -y apache2
      sudo systemctl start apache2
      sudo systemctl enable apache2
      echo "<h1>MultiCloud Demo AWS</h1>" | sudo tee /var/www/html/index.html
  • 5. VPC, 네트워크, EC2 및 보안그룹 구성 파일 생성 (aws-main.tf)
    • aws-main.tf 파일 내부에 아래 내용 추가
      #Initialize the AWS Provider
      provider "aws" {
        access_key = var.aws_access_key
        secret_key = var.aws_secret_key
        region = var.aws_region
      }
      #Create the VPC
      resource "aws_vpc" "aws-vpc" {
        cidr_block = var.aws_vpc_cidr
        enable_dns_hostnames = true
        enable_dns_support = true
        tags = {
          Name = "${var.app_name}-${var.app_environment}-vpc"
          Environment = var.app_environment
        }
      }
      #Define the subnet
      resource "aws_subnet" "aws-subnet" {
        vpc_id = aws_vpc.aws-vpc.id
        cidr_block = var.aws_subnet_cidr
        availability_zone = var.aws_az
        tags = {
          Name = "${var.app_name}-${var.app_environment}-subnet"
          Environment = var.app_environment
        }
      }
      #Define the internet gateway
      resource "aws_internet_gateway" "aws-gw" {
        vpc_id = aws_vpc.aws-vpc.id
        tags = {
          Name = "${var.app_name}-${var.app_environment}-igw"
          Environment = var.app_environment
        }
      }
      #Define the route table to the internet
      resource "aws_route_table" "aws-route-table" {
        vpc_id = aws_vpc.aws-vpc.id
        route {
          cidr_block = "0.0.0.0/0"
          gateway_id = aws_internet_gateway.aws-gw.id
        }
        tags = {
          Name = "${var.app_name}-${var.app_environment}-route-table"
          Environment = var.app_environment
        }
      }
      #Assign the public route table to the subnet
      resource "aws_route_table_association" "aws-route-table-association"{
        subnet_id = aws_subnet.aws-subnet.id
        route_table_id = aws_route_table.aws-route-table.id
      }
      #Define the security group for HTTP web server
      resource "aws_security_group" "aws-web-sg" {
        name = "${var.app_name}-${var.app_environment}-web-sg"
        description = "Allow incoming HTTP connections"
        vpc_id = aws_vpc.aws-vpc.id
        ingress {
          from_port = 80
          to_port = 80
          protocol = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
        }
        egress {
          from_port = 0
          to_port = 0
          protocol = "-1"
          cidr_blocks = ["0.0.0.0/0"]
        }
        tags = {
          Name = "${var.app_name}-${var.app_environment}-web-sg"
          Environment = var.app_environment
        }
      }
      #Get latest Ubuntu 18.04 AMI
      data "aws_ami" "ubuntu-18_04" {
        most_recent = true
        owners = ["099720109477"]
        filter {
          name = "name"
          values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
        }
        filter {
          name = "virtualization-type"
          values = ["hvm"]
        }
      }
      #Create Elastic IP for web server
      resource "aws_eip" "aws-web-eip" {
        vpc = true
        tags = {
          Name = "${var.app_name}-${var.app_environment}-elastic-ip"
          Environment = var.app_environment
        }
      }
      #Create EC2 Instances for Web Server
      resource "aws_instance" "aws-web-server" {
        ami = data.aws_ami.ubuntu-18_04.id
        instance_type = "t2.micro"
        subnet_id = aws_subnet.aws-subnet.id
        vpc_security_group_ids = [aws_security_group.aws-web-sg.id]
        associate_public_ip_address = true
        source_dest_check = false
        key_name = var.aws_key_pair
        user_data = file("aws-user-data.sh")
        tags = {
          Name = "${var.app_name}-${var.app_environment}-web-server"
          Environment = var.app_environment
        }
      }
      #Associate Elastic IP to Web Server
      resource "aws_eip_association" "aws-web-eip-association" {
        instance_id = aws_instance.aws-web-server.id
        allocation_id = aws_eip.aws-web-eip.id
      }
  • AWS Terraform 배포 확인됨

Azure에 WEB배포 (Terraform)

  • 1. Azure CLI 환경 구성
    • 아래 코드 실행
      apt update 
      apt install azure-cli
    • WSL2에선 az login명령이 안된다. → az login --device-codehttps://microsoft.com/devicelogin 으로 접속해서 명령 결과로 나온 코드 입력하면 로긴된다.
    • az account get-access-token명령으로 토큰 확인
      root@jwjung:/web_infra# az account get-access-token
      {
        "accessToken": "토큰 값",
        "expiresOn": "2021-02-02 18:50:14.764144",
        "subscription": "a573addd-d0a0-4748-a03b-81c5b067d960",
        "tenant": "테넌트 값",
        "tokenType": "Bearer"
      }

     

  • 2. Azure 변수 선언 및 설정값 추가 (azure-variables.tf)
    • azure-variables.tf 파일에 아래 내용 추가
      #Location Resource Group
      variable "rg_location" {
        type = string
        description = "Location of Resource Group"
        default = "Korea Central"
      }
      #VNET CIDR
      variable "azure_vnet_cidr" {
        type = string
        description = "Vnet CIDR"
        default = "10.2.0.0/16"
      }
      #Subnet CIDR
      variable "azure_subnet_cidr" {
        type = string
        description = "Subnet CIDR"
        default = "10.2.1.0/24"
      }
      #Linux VM Admin User
      variable "linux_admin_user" {
        type = string
        description = "Linux  VM Admin User"
        default = "tfadmin"
      }
      #Linux VM Admin Password
      variable "linux_admin_password" {
        type = string
        description = "Linux VM Admin Password"
        default = "S3cr3tP@ssw0rd"
      }
      #Linux VM Hostname
      variable "linux_vm_hostname" {
        type = string
        description = "Linux VM Hostname"
        default = "azwebserver1"
      }
      #Ubuntu Linux Publisher used to build VMs
      variable "ubuntu-linux-publisher" {
        type = string
        description = "Ubuntu Linux Publisher used to build VMs"
        default = "Canonical"
      }
      #Ubuntu Linux 18.x SKU used to build VMs
      variable "ubuntu-linux-18-sku" {
        type = string
        description = "Ubuntu Linux Server SKU used to build VMs"
        default = "18.04-LTS"
      }
  • 3. VM에 Apache 설치 및 index.html 구성을 위한 파일 생성 (azure-user-data.sh)
    • azure-user-data.sh 파일에 아래 내용 추가
      #! /bin/bash
      sudo apt-get update
      sudo apt-get install -y apache2
      sudo systemctl start apache2
      sudo systemctl enable apache2
      echo "<h1>MultiCloud Demo Azure</h1>" | sudo tee /var/www/html/index.html
  • 4. Virtual Network, Azure 네트워크, VM 및 보안그룹 구성 (azure-main.tf)
    • azure-main.tf 파일에 아래 내용 추가
      #Configure the Azure Provider
      provider "azurerm" {
        environment = "public"
        features {}
      }
      #Create Resource Group
      resource "azurerm_resource_group" "azure-rg" {
        name = "${var.app_name}-${var.app_environment}-rg"
        location = var.rg_location
      }
      #Create a virtual network
      resource "azurerm_virtual_network" "azure-vnet" {
        name = "${var.app_name}-${var.app_environment}-vnet"
        resource_group_name = azurerm_resource_group.azure-rg.name
        location = var.rg_location
        address_space = [var.azure_vnet_cidr]
        tags = {
          environment = var.app_environment
        }
      }
      #Create a subnet
      resource "azurerm_subnet" "azure-subnet" {
        name = "${var.app_name}-${var.app_environment}-subnet"
        resource_group_name  = azurerm_resource_group.azure-rg.name
        virtual_network_name = azurerm_virtual_network.azure-vnet.name
        address_prefix = var.azure_subnet_cidr
      }
      #Create Security Group to access Web Server
      resource "azurerm_network_security_group" "azure-web-nsg" {
        name = "${var.app_name}-${var.app_environment}-web-nsg"
        location = azurerm_resource_group.azure-rg.location
        resource_group_name = azurerm_resource_group.azure-rg.name
        security_rule {
          name = "AllowHTTP"
          description = "Allow HTTP"
          priority = 100
          direction = "Inbound"
          access = "Allow"
          protocol = "Tcp"
          source_port_range = "*"
          destination_port_range = "80"
          source_address_prefix = "Internet"
          destination_address_prefix = "*"
        }
        tags = {
          environment = var.app_environment
        }
      }
      #Associate the Web NSG with the subnet
      resource "azurerm_subnet_network_security_group_association" "azure-web-nsg-association" {
        subnet_id = azurerm_subnet.azure-subnet.id
        network_security_group_id = azurerm_network_security_group.azure-web-nsg.id
      }
      #Get a Static Public IP
      resource "azurerm_public_ip" "azure-web-ip" {
        name = "${var.app_name}-${var.app_environment}-web-ip"
        location = azurerm_resource_group.azure-rg.location
        resource_group_name = azurerm_resource_group.azure-rg.name
        allocation_method = "Static"
        tags = {
          environment = var.app_environment
        }
      }
      #Create Network Card for Web Server VM
      resource "azurerm_network_interface" "azure-web-nic" {
        name = "${var.app_name}-${var.app_environment}-web-nic"
        location = azurerm_resource_group.azure-rg.location
        resource_group_name = azurerm_resource_group.azure-rg.name
        ip_configuration {
          name = "internal"
          subnet_id = azurerm_subnet.azure-subnet.id
          private_ip_address_allocation = "Dynamic"
          public_ip_address_id = azurerm_public_ip.azure-web-ip.id
        }
        tags = {
          environment = var.app_environment
        }
      }
      #Create web server vm
      resource "azurerm_virtual_machine" "azure-web-vm" {
        name = "${var.app_name}-${var.app_environment}-web-vm"
        location = azurerm_resource_group.azure-rg.location
        resource_group_name = azurerm_resource_group.azure-rg.name
        network_interface_ids = [azurerm_network_interface.azure-web-nic.id]
        vm_size = "Standard_B1s"
        delete_os_disk_on_termination = true
        delete_data_disks_on_termination = true
        storage_image_reference {
          publisher = var.ubuntu-linux-publisher
          offer = var.ubuntu-linux-offer
          sku = var.ubuntu-linux-18-sku
          version = "latest"
        }
        storage_os_disk {
          name = "${var.app_name}-${var.app_environment}-web-vm-os-disk"
          caching = "ReadWrite"
          create_option = "FromImage"
          managed_disk_type = "Standard_LRS"
        }
        os_profile {
          computer_name  = var.linux_vm_hostname
          admin_username = var.linux_admin_user
          admin_password = var.linux_admin_password
          custom_data = file("azure-user-data.sh")
        }
        os_profile_linux_config {
          disable_password_authentication = false
        }
        tags = {
          environment = var.app_environment
        }
      }
      #Output
      output "azure-web-server-external-ip" {
        value = azurerm_public_ip.azure-web-ip.ip_address
      }

     

GCP에 WEB배포 (Terraform)

  • 1. GCP 변수 선언 및 설정값 추가 (gcp-variables.tf)
    • gcp-variables.tf파일에 아래 내용 추가
      #GCP authentication file
      variable "gcp_auth_file" {
        type = string
        description = "GCP authentication file"
      }
      #Define GCP project name
      variable "gcp_project" {
        type = string
        description = "GCP project name"
      }
      #Define GCP region
      variable "gcp_region" {
        type = string
        description = "GCP region"
        default = "europe-west1"
      }
      #Define GCP zone
      variable "gcp_zone" {
        type = string
        description = "GCP zone"
        default = "europe-west1-b"
      }
      #Define subnet CIDR
      variable "gcp_subnet_cidr" {
        type = string
        description = "Subnet CIDR"
        default = "10.3.1.0/24"
      }
  • 2. GCP 인증정보 발급
    1. 사용자 인증정보 서비스 접속 → 사용자 인증 정보 만들기 클릭
    1. 서비스 계정 이름 지정 → 완료
    1. IAM → 서비스 계정 → 키만들기
    1. JSON 파일로 키 생성 및 저장
    1. WSL2에서 작업 디렉터리에 cp 명령으로 json파일 복사
  • 3. GCP 인증정보 파일 생성 (terraform.tfvars)
    • terraform.tfvars파일에 아래 내용 추가
      #GCP authentication variables
      gcp_project = "your-gcp-project"
      gcp_auth_file = "your-gcp-auth-file.json"
  • 4. Compute Engine에 Apache 설치 및 index.html 구성을 위한 파일 생성 (gcp-user-data.sh)
    • gcp-user-data.sh파일에 아래 내용 추가
      #! / bin / bash 
      sudo apt-get update
      sudo apt update 
      sudo apt install -y apache2 
      sudo systemctl start apache2 
      sudo systemctl enable apache2 
      echo "<h1> MultiCloud Demo Google Cloud </ h1>"| sudo tee /var/www/html/index.html
  • 5. VPC, GCP 네트워크, Compute Engine 및 보안그룹 구성 (gcp-main.tf)
    • gcp-main.tf파일에 아래 내용 추가
      #Setup the GCP provider
      terraform {
        required_version = ">= 0.12"
      }
      provider "google" {
        project = var.gcp_project
        credentials = file(var.gcp_auth_file)
        region = var.gcp_region
        zone = var.gcp_zone
      }
      #Create VPC
      resource "google_compute_network" "gcp-vpc" {
        name = "${var.app_name}-${var.app_environment}-vpc"
        auto_create_subnetworks = "false"
        routing_mode = "GLOBAL"
      }
      #Create subnet
      resource "google_compute_subnetwork" "gcp-subnet" {
        name = "${var.app_name}-${var.app_environment}-subnet"
        ip_cidr_range = var.gcp_subnet_cidr
        network = google_compute_network.gcp-vpc.name
        region = var.gcp_region
      }
      #Firewall allow http
      resource "google_compute_firewall" "gcp-allow-http" {
        name = "${var.app_name}-${var.app_environment}-fw-allow-http"
        network = google_compute_network.gcp-vpc.name
        allow {
          protocol = "tcp"
          ports = ["80"]
        }
        target_tags = ["http"]
      }
      #Create a public ip for web server
      resource "google_compute_address" "gcp-web-ip" {
        name = "${var.app_name}-${var.app_environment}-web-ip"
        project = var.gcp_project
        region = var.gcp_region
      }
      #Define bootstrap file
      data "template_file" "metadata_startup_script" {
        template = file("gcp-user-data.sh")
      }
      #Create VM for web server
      resource "google_compute_instance" "gpc-web-server" {
        name = "${var.app_name}-${var.app_environment}-web-server"
        machine_type = "f1-micro"
        zone = var.gcp_zone
        tags = ["http"]
        boot_disk {
          initialize_params {
            image = "ubuntu-os-cloud/ubuntu-1804-lts"
          }
        }
        #metadata_startup_script = data.template_file.metadata_startup_script.rendered  #부트 스트랩으로 넣는 방식이 인식이 안됨
        metadata = {
          startup-script = <<SCRIPT
              sudo apt-get update
              sudo apt update 
              sudo apt install -y apache2 
              sudo systemctl start apache2 
              sudo systemctl enable apache2 
              echo "<h1> MultiCloud Demo Google Cloud </ h1>"| sudo tee /var/www/html/index.html
              SCRIPT
        }
        network_interface {
          network = google_compute_network.gcp-vpc.name
          subnetwork = google_compute_subnetwork.gcp-subnet.name
          access_config {
            nat_ip = google_compute_address.gcp-web-ip.address
          }
        }
      }
      #Output
      output "external-ip-gcp-web-server" {
        value = google_compute_address.gcp-web-ip.address
      }

     

Cloudflare

  • 1. Cloudflare 자격 증명 정보 추가 (terraform.tfvars)
    • terraform.tfvars파일에 아래 내용 추가
      #Cloudflare 인증 변수 
      cloudflare_email = "[이메일]" 
      cloudflare_api_key = "[api 키]" 
      cloudflare_zone_id = "[zone-id]"
  • 2. Provider를 초기화하고 www 및 Root DNS 레코드를 생성 (cloudflare-dns.tf)
    • 원래는 아래 사진처럼 변수를 통해 사용하는데 변수를 못 읽어와서 직접 값 입력했다.
    • cloudflare-dns.tf파일에 아래 내용 추가
      #Define Cloudflare provider 
      provider "cloudflare" {
        email = "메일"
        api_key = "API 키"
      }
      #Create www record for Amazon Web Services
      resource "cloudflare_record" "aws-www" {
        zone_id = "zone 아이디"
        name = "www"
        value = aws_eip.aws-web-eip.public_ip
        type = "A"
        proxied = true
        depends_on = [aws_eip.aws-web-eip]
      }
      #Create www record for Azure
      resource "cloudflare_record" "azure-www" {
        zone_id = "zone 아이디"
        name = "www"
        value = azurerm_public_ip.azure-web-ip.ip_address
        type = "A"
        proxied = true
        depends_on = [azurerm_public_ip.azure-web-ip]
      }
      #Create www record for Google Cloud
      resource "cloudflare_record" "gcp-www" {
        zone_id = "zone 아이디"
        name = "www"
        value = google_compute_address.gcp-web-ip.address
        type = "A"
        proxied = true
        depends_on = [google_compute_address.gcp-web-ip]
      }
      #Create root record for Amazon Web Services
      resource "cloudflare_record" "aws-root" {
        zone_id = "zone 아이디"
        name = "@"
        value = aws_eip.aws-web-eip.public_ip
        type = "A"
        proxied = true
        depends_on = [aws_eip.aws-web-eip]
      }
      #Create root record for Azure
      resource "cloudflare_record" "azure-root" {
        zone_id = "zone 아이디"
        name = "@"
        value = azurerm_public_ip.azure-web-ip.ip_address
        type = "A"
        proxied = true
        depends_on = [azurerm_public_ip.azure-web-ip]
      }
      #Create root record for Google Cloud
      resource "cloudflare_record" "gcp-root" {
        zone_id = "zone 아이디"
        name = "@"
        value = google_compute_address.gcp-web-ip.address
        type = "A"
        proxied = true
        depends_on = [google_compute_address.gcp-web-ip]
      }
      #Output AWS
      output "cloudflare-aws-www-record-id" {
        value = cloudflare_record.aws-www.id
      }
      output "cloudflare-aws-www-record-hostname" {
        value = cloudflare_record.aws-www.hostname
      }
      output "cloudflare-aws-root-record-id" {
        value = cloudflare_record.aws-root.id
      }
      output "cloudflare-aws-root-record-hostname" {
        value = cloudflare_record.aws-root.hostname
      }
      # Output Azure
      output "cloudflare-azure-www-record-id" {
        value = cloudflare_record.azure-www.id
      }
      output "cloudflare-azure-www-record-hostname" {
        value = cloudflare_record.azure-www.hostname
      }
      output "cloudflare-azure-root-record-id" {
        value = cloudflare_record.azure-root.id
      }
      output "cloudflare-azure-root-record-hostname" {
        value = cloudflare_record.azure-root.hostname
      }
      # Output GCP
      output "cloudflare-gcp-www-record-id" {
        value = cloudflare_record.gcp-www.id
      }
      output "cloudflare-gcp-www-record-hostname" {
        value = cloudflare_record.gcp-www.hostname
      }
      output "cloudflare-gcp-root-record-id" {
        value = cloudflare_record.gcp-root.id
      }
      output "cloudflare-gcp-root-record-hostname" {
        value = cloudflare_record.gcp-root.hostname
      }

실행 & 결과

  • terraform init명령 실행
  • terraform plan으로 확인 후 terraform apply로 프로비저닝

  • 프로비저닝 및 확인
    • terraform applyyes 를 통해서 인프라를 프로비저닝 한다.
    • cloudflare에 레코드가 정상적으로 등록되었다.
    • 접속화면들
    • 여러 벤더사의 웹서버가 하나의 도메인으로 제공되는 것을 확인했다. 유료 결제를 통해 Failover 기능을 갖추면 벤더사의 장애에 관계없이 가장 가용성 높은 웹서버를 제공할 수 있을 것 같다.
  • 리소스 삭제
    • terraform destroyyes 명령으로 너무 간단하게 모든 리소스가 제거된다.
    • 클라우드 올챙이 시절을 겪은 사람이라면 실습 후에 리소스 삭제를 제대로 하지 않아서 불필요한 과금을 한 경험이 있을것이다. Terraform은 삭제의 번거로움과 놓치는 리소스로부터 자유롭다.

결론

  • AWS, Azure, GCP를 통해 웹서버 인스턴스의 멀티클라우드 실습을 성공적으로 마쳤다. 단순하게 서버의 인스턴스를 멀티클라우딩하여 가용성을 극대화하는 것을 넘어, 인프라와 플랫폼을 나누거나 플랫폼마다 제공하는 다양한 강력한 서비스를 섞어서 사용하는 것에 멀티클라우딩의 목적이 있을 것 같다.
  • 또한 대부분의 기업의 최종 목표가 멀티클라우드 환경이라고는 하지만 모든 전략에 장단점이 있듯이 어려운 설계 및 관리를 비롯한 단점 또한 있을것이다.

 

Comments