본문 바로가기

KOSA 클라우드 솔루션즈 아키텍트 양성과정

[6.30] CloudFormation, Terraform(yaml , 스택, tf )

CloudFormation을 위한 yaml 파일 작성

- 주요 섹션 설명
1. Resources(생성) : AWS 인프라의 실질적인 섹션입니다. EC2 인스턴스, S3 버킷, ELB등과 같은 클라우드 포메이션을 이용해 AWS 웹 콘솔에서 실행하는 것으로 거의 모든 리소스 유형을 생성할 수 있습니다. 하지만 신규 또는 최첨단의 AWS 리소스는 즉시 제공되지 않는 경우가 종종 있습니다. 리소스에는 기본 반환값이 있습니다. Ref를 이용해 이 반환값을 얻어올 수 있고 템플릿의 다른 위치에 사용할 수 있습니다. 예를 들어 AWS::EC2::VPC 리소스 유형은 기본 반환값을 갖고 있고 이 값은 VPC의 ID 입니다.

2. Parameters(입력) : 명령줄 도구에 입력하는 매개변수와 동일하게 스택을 만들거나 업데이트할 때 정의하는 입력값입니다. 파라미터는 템플릿의 변경 없이도 스택을 커스터마이즈할 수 있게 해줍니다. AMI ID, VPC ID, Subnet ID등과 같은 매개변수를 사용할 수 있습니다.

3. Output(출력) : 스택이 완료된 후에 결과물을 출력하려고 할때 유용합니다. 예를 들어 ELB의 퍼블릭 URL이나 EC2의 퍼블릭 IP를 출력할 수 있습니다.

4. Mapping(지정) : 리전의 특화된 템플릿에서 어떠한 요소를 참조할 때 필요합니다. 예를 들어 템플릿에 EC2 AMI ID에 대한 매핑을 지정하는 것입니다. AMI ID가 리전에 특화된 리소스이기 때문에 유효한 AMI ID를 리전별로 지정하려고 할때 사용합니다.

 

yaml 파일 형태로 템플릿 작성(Resources)

# vi new-vpc.yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:                           => 섹션 명
  VPC:                                   => 논리적 ID : 임의로 작성 가능, 논리적 아이디는 !Ref VPC처럼 내장 함수를 통해 참조 가능
    Type: AWS::EC2::VPC      => 필드 : VPC와 관련한 특징
    Properties:
      CidrBlock: 192.168.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true  => 로컬 DNS를 세팅 : false로 인스턴스에서 ping 불가능
      InstanceTenancy: default   => 인스턴스를 공용 자원으로 사용하므로, default값 그대로 설정
      Tags:
        - Key: Name
          Value: NEW-VPC
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-2a
      VpcId: !Ref VPC                           => subnet은 VPC에 속해 있기 때문에, VPC의 ID가 필요
      CidrBlock: 192.168.0.0/20
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: NEW-PUBLIC-SUBNET-2A
  SubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-2b
      VpcId: !Ref VPC
      CidrBlock: 192.168.16.0/20
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: NEW-PUBLIC-SUBNET-2B
  SubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-2c
      VpcId: !Ref VPC
      CidrBlock: 192.168.32.0/20
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: NEW-PUBLIC-SUBNET-2C
  SubnetD:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-2d
      VpcId: !Ref VPC
      CidrBlock: 192.168.48.0/20
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: NEW-PUBLIC-SUBNET-2D
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: NEW-IGW
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  RouteTableA:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: NEW-PUBLIC-RTB
  InternetRoute:                            
    Type: AWS::EC2::Route
    DependsOn: InternetGateway                    => 인터넷 게이트웨이에 의존 : IGW를 만들고 라우터를 만들어야 한다는 뜻
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTableA                => InternetRoute를 Route TableA에 넣음
  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation    => 퍼블릭 라우팅 테이블 생성 후 서브넷을 명시적으로 연결
    Properties:
      RouteTableId: !Ref RouteTableA
      SubnetId: !Ref SubnetA
  SubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTableA
      SubnetId: !Ref SubnetB
  SubnetCRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTableA
      SubnetId: !Ref SubnetC
  SubnetDRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTableA
      SubnetId: !Ref SubnetD

 

위의 명령어를 yaml 파일로 저장, 이렇게 형식을 갖춰 만든 파일을 템플릿이라고 함


CloudFormation 콘솔에서 yaml 템플릿 업로드

파일을 업로드하면 CloudFormation 자체에는 저장장치가 없기 때문에 자동으로 S3에 버킷이 생성되면서 업로드 됨

 

업로드하면서 S3에 자동으로 버킷이 생성된 것 확인, CloudFormation 지워도 S3는 남음

 

스택 : 작업의 순서

 

CloudFormation 생성 후 yaml파일에 따른 작업 진행 상황 확인 가능


CloudFormation(Mappings)

# vi new-ec2.yaml
AWSTemplateFormatVersion: 2010-09-09
Mappings:                                                           => 리전 별로 특수한 리소스가 있을 때, 리전과 해당 리소스를 맵핑
  RegionMap:                      
    ap-northeast-2:
      AMIID: ami-0fd0765afb77bcca7                    => 서울 리전의 AMI ID, AMI ID와 해당 리전을 맵핑
    ap-northeast-1:                                                 
      AMIID: ami-0b7546e839d7ace12                   => 도쿄 리전의 AMI ID, AMI ID와 해당 리전을 맵핑
Parameters:                                                          => 빈번하게 변경할 값을 Parameters로 넣으면, yaml파일 수정X
  InstanceTypeParameter:                                    
    Type: String                                                       => 입력 값을 문자열로 받을 것이기에 String으로 설정
    Default: t2.micro                                                => Default를 주석 처리하면 해당 칸이 비어 있게 됨
    Description: Enter instance size. Default is t2.micro
  VPC:
    Type: String
    Default: vpc-01a276b266db7833b
    Description: VPC ID.
  Subnet:
    Type: String
    Default: subnet-01132b9dddcf71d3d
    Description: Subnet ID.
  AMI:
    Type: String
    Default: AMIID                              => 특별한 값을 넣지 않는다면 위에서 리전 별로 맵핑한 AMIID를 사용
    Description: The Linux AMI to use.
  Key:
    Type: String
    Default: new-key
    Description: The key used to access the instance.
Resources:
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "NEW-SG-WEB"
      GroupDescription: "NEW-SG-WEB"                           => GroupName을 그대로 복사해서 사용하는 것이 편함
      VpcId: !Ref VPC                                                       => 레퍼런스 내장 함수를 통해 VPCID를 가져옴
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: 123.142.252.25/32
      SecurityGroupEgress:
        - IpProtocol: -1                                        => -1은 모든 프로토콜을 의미
          CidrIp: 0.0.0.0/0
  Linux:
    Type: 'AWS::EC2::Instance'
    Properties:                    => Properies 안에서의 순서는 관계 없음
      SubnetId: !Ref Subnet
#      ImageId: !Ref AMI
      ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", !Ref AMI ]   

      => !FindInMap은 Mappings에서 가져오는 함수, Mapping의  RegionMap 논리적ID에서 AMI를 참고해옴
      InstanceType:
        Ref: InstanceTypeParameter     => 이것을 한 줄로 표현하면 Instance Type: !Ref InstanceTypeParameter
      KeyName: !Ref Key
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
      Tags:
        - Key: Name
          Value: NEW-EC2

      UserData:
        Fn::Base64: |  (: |개행 문자 없이 엔터)  => 인코딩 과정에서 글자가 깨지는 경우를 방지하기 위해 사용하는 내장 함수
          #cloud-boothook
          #!/bin/bash
          yum install -y httpd
          systemctl enable --now httpd
          echo "Hello World!" > /var/www/html/index.html
Outputs:  
  PublicIp:    
    Description: PublicIp Output    
    Value: {"Fn::GetAtt": ["Linux","PublicIp"]}  => Linux라는 논리적 ID를 통해서 만들어진 EC2의 정보 중 PublicIp 추출


CloudFormation 콘솔에서 yaml 템플릿 업로드

 

 

yaml 파일에서 파라미터로 지정한 것들을 직접 입력 가능

 

스택 생성 진행


Terraform 기초
  • 테라폼은 해시코프사에서 Go언어로 개발한 오픈소스도구
  • 운영체제마다 바이너리 파일이 존재, Go 코드는 하나의 바이너리 파일로 컴파일되며 Terraform이라는 명령어로 실행
  • Terraform 명령어를 사용하여 노트북, 데스크탑, 빌드 서버 또는 다른 컴퓨터에서든 인프라를 배포
    • => 이를 위해 추가 인프라(마스터, 에이전트)를 생성할 필요가 없음
  • 즉 Terraform 명령어가 AWS, Azure, GCP, Openstack 등의 Provider를 대신해 API를 호출하여 리소스를 생성
  • 테라폼은 생성하려는 인프라 정보가 담겨 있는 텍스트로 이루어진 테라폼 구성 파일을 생성하여 API를 호출
    • => 이러한 구성 값들이 '코드형 인프라'를 만드는 바로 그 '코드'임
  • 인프라를 수정하고자 할 때, 서버에 직접 접속하거나 수작업으로 하는 대신 테라폼을 사용하여 구성 파일을 수정 가능

 

** 프로비저닝, 관리, 서버 템플릿, 오케스트레이션 **

* 프로비저닝 도구 : 테라폼(클라우드 네이티브), CloudFormation(AWS), Heat(오픈스택)
  => 서버 자체를 생성
* 구성 관리 도구 : 앤서블
  => on-prem에서 시작한 범용, 서버 생성도 가능, 이미 존재하는 서버를 인벤토리에 넣고 관리하는 역할을 했었음
* 서버템플릿 : 도커
* 오케스트레이션 : 쿠버네티스

* 테라폼 워크플로 3단계 :

- 쓰기(~.tf : terraform configuration, terraform state File) => 계획(VPC, SG를 만드는 등 테스트) => 적용(클라우드 플랫폼)


Terraform 간단 설명

# vi main.tf
provider "aws" { # aws를 공급자로 사용하여
  region = "ap-northeast-2" # 서울 리전에 인프라를 배포한다는 의미
}
resource "aws_instance" "example" {
  ami           = "ami-0fd0765afb77bcca7"
  instance_type = "t2.micro"
}

resource "<PROVIDER>_<TYPE>" "<NAME>" { # PROVIDER는 aws 같은 공급자의 이름이고 TYPE은 instance 같이 해당 공급자에서 생성할 리소스 유형입니다. NAME은 테라폼 코드에서 이 리소스를 참조하기 위해 사용할 수 있는 example과 같은 '식별자'입니다. CONFIG는 특정 리소스에 대한 하나 이상의 인수(argument)로 구성됩니다.
  [CONFIG ...]
}

=> <NAME>은 CloudFormation의 논리적 ID와 유사한 역할


--- Terraform 설치
# wget https://releases.hashicorp.com/terraform/1.2.3/terraform_1.2.3_linux_amd64.zip
# unzip terraform_1.2.3_linux_amd64.zip
# mv terraform /usr/local/bin/
# terraform -version

--- AWS Terraform ec2 인스턴스 생성
# mkdir aws && cd $_
# vi main.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0fd0765afb77bcca7"
  instance_type = "t2.micro"
}

# terraform init
# terraform plan                   => main파일에 논리적 오류, 오타가 없는지 검증
# terraform apply

--- ec2 인스턴스 웹서버 배포
# vi main.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "example" {
  ami                    = "ami-0fd0765afb77bcca7"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id] 
  => resource에서 생성된 보안 그룹 "aws_security_group" "instance"(식별자) 에서 보안 그룹의 id를 가져옴
  key_name  = "new-key"
  user_data = <<-EOF
              #! /bin/bash
              yum install -y httpd
              systemctl enable --now httpd
              echo "Hello, Terraform" > /var/www/html/index.html
              EOF

  tags = {
    Name = "terraform-example"
  }
}

resource "aws_security_group" "instance" {

  name = var.security_group_name                       
=>  name : 태그가 아닌 보안 그룹 이름을 의미,
=> var : 변수를 정의하는 부분이 아래 어딘가에 있다는 의미, 해당 변수를 참조하겠다는 의미
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["123.142.252.25/32"]
  }
  ingress {
    from_port   = -1
    to_port     = -1
    protocol    = "icmp"
    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 = "terraform-sg"
  }
}

variable "security_group_name" {                       => 위의 var.security_group_name이 여기 나온 변수를 찾음
  description = "The name of the security group"
  type        = string
  default     = "terraform-example-instance"         => 위의 var.security_group_name이 default를 참조함
}

output "public_ip" {
  value       = aws_instance.example.public_ip
  description = "The public IP of the Instance"
}

output "public_dns" {
  value       = aws_instance.example.public_dns
  description = "The Public dns of the Instance"
}

output "private_ip" {
  value       = aws_instance.example.private_ip
  description = "The Private_ip of the Instance"
}

# terraform init
# terraform plan
# terraform apply
# terraform output public_ip
# terraform destroy