Windows 운영체제를 오래 사용하던 사람이라면 아마도 제어판에서 프로그램을 추가 및 삭제하는 방법이 어느정도 익숙하지 않을까 싶다. "프로그램 추가/제거"라는 이전 이름이 어느 순간 "프로그램 및 기능"으로 변경되었고, 이를 통해 설치된 버전이 무엇인지 살펴보고 변경 및 제거를 할 수가 있다.

 

Windows 11 환경에서 "프로그램 및 기능" 메뉴에서 "AWS" 키워드로 검색한 결과

 

Windows 11 환경에서 "프로그램 및 기능" 메뉴에서 "AWS" 키워드로 검색한 결과, 현재 설치된 AWS CLI 버전은 2.15.9.0임을 확인할 수 있었다. AWS CLI는 AWS 서비스를 명령어를 통해 관리하도록 해주는 CLI 도구로, `aws configure`를 실행하여 Access Key ID, Secret Access Key, 리전 이름 정도만 설정하면 AWS 서비스를 손쉽게 제어할 수가 있다. 설치할 당시에는 공식 홈페이지 (https://aws.amazon.com/ko/cli/)에서 다운로드를 하여 직접 설치를 진행하였는데, 시간이 지나면서 설치된 버전이 최신인지 확인하고, 최신 버전으로 업그레이드하는 과정이 필자에게는 다소 번거롭게 느껴졌다.

Ubuntu에서 사용하던 "sudo apt update && sudo apt upgrade"와 같이 손쉽게 할 수 있다면 얼마나 좋을까?

 

문득 갑자기 이런 궁금증이 들었고, winget이라는 도구가 있다는 잊고 있던 생각이 갑자기 났다. 이를 통해 AWS CLI 버전을 최신으로 업그레이드를 진행한 경험을 블로그를 통해 적고자 한다.

 

1. winget 소개 & 설치

winget은 Windows 10 이상에서 사용 가능한 패키지 관리 도구로, 다양한 소프트웨어를 손쉽게 설치, 업그레이드, 제거할 수 있다. CLI 명령어를 통해 소프트웨어 관리를 자동화할 수도 있겠다. Microsoft Store에서 앱 설치 관리자를 직접 검색하여 설치하거나, 또는 https://apps.microsoft.com/detail/9nblggh4nns1?rtc=1&hl=ko-kr&gl=KR#activetab=pivot:overviewtab URL로 접속하여 "설치" 버튼을 클릭하여 설치할 수 있다.

 

winget: 앱 설치 관리자를 Microsoft Store에서 설치하기

 

2. winget 기본 명령어

winget을 사용하는 기본적인 방법은 apt와 많이 유사하다. 필자는 여전히 "sudo apt update && sudo apt upgrade" 명령어가 익숙한데, winget에서는 update와 upgrade 명령어가 동일하다는 점을 참고하자 (정확히는 update가 upgrade에 대한 alias로 구성되어 있다). 자세한 도움말은 https://aka.ms/winget-command-help 를 통해 확인할 수 있으며, 또는 국내 Microsoft 권순만 MVP께서 정리하신 블로그 글 (URL: https://blog.naver.com/hakunamata2/223366445380 )을 참고해도 좋겠다.

winget vs. apt (많이 유사해보이지 않나요?!)

 

3. AWS CLI 설치하기

AWS 공식 문서 (URL: https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html)에는 MSI 설치 프로그램을 다운로드하여 실행하는 방법을 안내하고 있으며, winget으로도 손쉽게 설치가 가능하다. 커뮤니티를 통해 운영이 이루어지는 https://winget.run 사이트에서 AWS CLI 설치 명령어가 잘 안내되어 있다. 다만 본 블로그를 작성하는 시점에 AWS CLI 최신 버전은 2.16.5인데 반해, 해당 사이트에서는 AWS CLI 최신 버전이 2.11.2라고 언급되어 있다. 해당 사이트에서는 최신 정보를 포함하고 있지 않을 수도 있음을 꼭 참고하자.

https://winget.run/pkg/Amazon/AWSCLI 에서 안내하는 AWS CLI 설치 방법 (주의: 최신 버전 정보를 포함하지 않는 것 같습니다)

 

4. AWS CLI 최신 버전으로 업그레이드 및 관리하기

만약 본인처럼 이미 기존 AWS CLI 버전이 설치되지 않았다면 위 3번에서 소개하는 install 옵션을 사용하여 바로 최신 버전으로 설치할 수가 있다. 사실 다음과 같이 단순하게 설치 명령어를 실행하여 최신 버전을 다운로드하여 설치할 수 있다.

 

> winget install Amazon.AWSCLI

 

설치된 AWS CLI 버전을 확인하기 위해서는 AWS CLI를 "--version" 옵션과 함께 다음과 같이 직접 실행하면 된다.

> aws --version
aws-cli/2.15.9 Python/3.11.6 Windows/10 exe/AMD64 prompt/off

 

또 다른 방법으로는 Ubuntu에서 "apt list | grep AWS"와 같이 실행하는 것과 같이 PowerShell에서는 다음과 같은 명령어를 통해 확인할 수도 있겠다. 이 방법은 현재 설치된 버전이 2.15.9.0이며, 최신 설치 가능한 버전이 2.16.4라는 정보까지를 같이 알 수가 있다는 장점이 있다.

> winget list | Select-String -Pattern AWS

Freedom Scientific JAWS 2024            FreedomScientific.JAWS.2024              2024.2310.70.400     2024.240??winget
Freedom Scientific JAWS Training Table??{AE1E7553-752E-4D04-9695-EE1FB83C54AE}   25.0.2005.0
AWS Command Line Interface v2           Amazon.AWSCLI                            2.15.9.0             2.16.4    winget

 

최신 버전으로 업데이트하기 위해서는 다음과 같이 명령어를 실행한다.

> winget upgrade Amazon.AWSCLI

 

 

최신 버전이 정말 설치되었는지 확인 완료까지 해보았다!

> aws --version
aws-cli/2.16.4 Python/3.11.8 Windows/10 exe/AMD64

 

제거하는 방법은 다음과 같다.

> winget uninstall Amazon.AWSCLI

 

이렇게 특정 패키지를 winget 명령어를 통해 설치, 업그레이드, 제거까지 손쉽게 하는 방법에 대해 알아보았다.

5. winget 명령어로 여러 패키지 업그레이드하기

사실 원래는 AWS CLI 툴 자체를 업그레이드하는 목적보다는 이전에 https://github.com/ianychoi/aws-ecs-fargate-dotnet-module3-supplementary 와 같은 내용을 준비하면서 "Build Tools for Visual Studio 2019"가 설치되었던 것 같은데, 해당 패키지를 포함해 AWS CLI 등 여러 버전들을 각각 다운로드받아 최신 버전으로 설치하기가 번거롭다보니 winget을 사용하면 어떨까하는 생각이 들었고 "winget upgrade" 명령어를 통해 손쉽게 최신 버전으로 업데이트를 진행할 수 있었다. "winget upgrade" 를 실행하면 업그레이드 가능한 모든 프로그램들이 나온다. 필자의 경우 이전에 영상 자막을 직접 관리하고자 사용했던 Vrew 프로그램이 오래 되었고, Bandizip, GIMP 등도 업그레이드하지 않던 상황을 손쉽게 확인할 수 있었다. 모든 것을 업그레이드하려면 "winget upgrade --all" 명령어를 사용하면 되겠지만, 꼭 모든 소프트웨어를 업그이드할 필요는 없겠다. 필자의 경우 GIMP를 굳이 최신으로 꼭 업그레이드가 필요한가 싶어 다음과 같이 3개 소프트웨어만 업데이트를 진행하였다.

"winget upgrade"를 실행한 결과 (일부)
3개 패키지에 대해 명시적으로 업그레이드하기

 

 

이와 같이 winget을 사용해 AWS CLI를 설치, 업그레이드, 제거하는 방법을 살펴보았다. 설치된 소프트웨어를 최신 버전으로 유지함으로써 최신 기능을 활용할 뿐만 아니라 보안 취약점을 방지할 수 있으므로, 정기적으로 업그레이드를 확인하는 것이 좋은데 winget을 통해 AWS CLI 뿐만 아니라 설치된 여러 소프트웨어의 설치된 버전 및 최신 버전을 손쉽게 확인하고 명령어를 통해 쉽게 업그레이드가 가능한 장점을 잘 활용해보았으면 한다.

본 게시물은 CloudNet@에서 진행하는 AEWS (Amazon EKS Workshop Study) 스터디에 참여하며 정리한 내용입니다.

 

어느덧 AEWS 마지막 스터디 파트에 왔다. 인프라를 코드라는 관점에서 바라보고 관리한다는 Infrastructure as Code를 줄여 부르는 IaC를 EKS에 적용하는 부분에 대한 스터디이다. 이 스터디에서는 Terraform을 사용한다.

 

Terraform은 Hashicorp 회사에서 공개한 오픈 소스로, 2023년 8월 경에 MPL (Mozilla Public License)에서 BSL (Business Source License)로 변경이 이루어졌다 (관련 정보: Hashicorp 홈페이지 - License FAQ 영문 사이트 링크). HashiCorp Configuration Language에 따라 인프라를 코드 형태로 작성하면 계획 (plan) 후 적용 (apply)하는 방식으로 동작이 이루어진다.

 

Terraform 동작 과정 그림 (출처: Hashicorp 홈페이지 - https://developer.hashicorp.com/terraform/intro )

 

0. 실습 환경 배포 & 소개

WSL2에서 동작하는 Ubuntu 20.04 LTS 환경에서 실습을 진행하였다. 

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# 테라폼 버전 정보 확인
terraform version

 

추가로 AWS CLI, eksctl, kubectl, Helm을 아래 각 링크를 참고하여 설치한다.

실습에 편리한 툴 중에서 jq 도 설치해보았다. macOS에서는 watch, tree 등을 brew로 설치하는 것도 좋다고 한다.

# Linux
sudo apt install -y tree jq

 

또한 아래 명령어를 실행하였을 때 VPC ID가 출력되는지 확인해본다.

aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq '.Vpcs[0].VpcId'

 

만약 VPC ID가 출력되지 않는다면 아래 명령어를 실행하여 default VPC를 생성한다.

# default VPC를 생성
aws ec2 create-default-vpc

# default Subnet 생성
aws ec2 create-default-subnet --availability-zone ap-northeast-2a
aws ec2 create-default-subnet --availability-zone ap-northeast-2b
aws ec2 create-default-subnet --availability-zone ap-northeast-2c
aws ec2 create-default-subnet --availability-zone ap-northeast-2d

 

그리고 Visual Studio Code (줄여서 VSCode) 를 사용하는데, 이 때 본인은 WSL 환경에서 실습을 하고 있으므로, VSCode가 WSL 환경을 인지해야 한다. 이 부분은 WSL이라는 확장팩 설치 및 왼쪽 하단을 클릭하여 "Connect to WSL"을 실행하여 연결이 되어야 한다는 점을 참고하자.

 

또한 HashiCorp HCLHashiCorp Terraform 확장을 설치하자.

1. Terraform 기본 사용

1.1. Terraform 기본 환경 및 사용해보기

 

먼저 아래와 같이 기본 환경을 준비해보자.

mkdir learn-terraform
cd learn-terraform
touch main.tf

 

그리고 Amazon Linux 2 최신 ami id를 찾아 AL2ID 환경변수에 저장해두자. "ami-0217b147346e48e84" 값을 얻을 수 있었다.

#aws ec2 describe-images --owners self amazon
aws ec2 describe-images --owners self amazon --query 'Images[*].[ImageId]' --output text
aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available"
aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId, Name]' --output text
ami-0217b147346e48e84	amzn2-ami-hvm-2.0.20240412.0-x86_64-gp2

aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId]' --output text
ami-0217b147346e48e84

AL2ID=`aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId]' --output text`
echo $AL2ID

 

또한 EC2가 생성이 되는지 모니터링을 하는 명령어를 별도 터미널로 실행해두도록 하자.

# [터미널1] EC2 생성 모니터링
export AWS_PAGER=""
while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done

 

 

테라폼 코드를 작성해보자. 아래 코드를 보면 provider와 resource 영역으로 나뉘어 있는데, provider는 Terraform으로 정의할 Infrastructure Provider를 의미하고, resource는 실제로 생성할 인프라 자원을 의미한다.

 

cat <<EOT > main.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "example" {
  ami           = "$AL2ID"
  instance_type = "t2.micro"
}
EOT

 

이제 배포와 실행을 해 보도록 하자.

 

# 초기화
terraform init
ls -al
tree .terraform

# plan 확인
terraform plan

# apply 실행
terraform apply
 Enter a value: yes 입력

# ec2 생성 확인 : aws 웹 관리 콘솔에서도 확인 - 서울 리전 선택
export AWS_PAGER=""
aws ec2 describe-instances --output table

# 테라폼 정보 확인
terraform state list
terraform show
terraform show aws_instance.example

 

 

이번에는 태그 정보를 수정하고 적용해보자.

cat <<EOT > main.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "example" {
  ami           = "$AL2ID"
  instance_type = "t2.micro"

  tags = {
    Name = "aews-study"
  }

}
EOT

 

# plan 실행 시 아래와 같은 정보가 출력
terraform plan
# aws_instance.example will be updated in-place
  ~ resource "aws_instance" "example" {
        id                                   = "i-0fe5acad038030055"
      ~ tags                                 = {
          + "Name" = "aews-study"
        }
      ~ tags_all                             = {
          + "Name" = "aews-study"
        }
        # (38 unchanged attributes hidden)

        # (8 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

# apply 실행
terraform apply
 Enter a value: yes 입력

# 모니터링 : [터미널1]에 Name 확인

 

 

실습을 완료하였으면 리소스를 삭제하자.

 

# 리소스 삭제
terraform destroy
 Enter a value: yes 입력

혹은
terraform destroy -auto-approve

 

1.2. HCL 및 프로바이더, 변수 및 우선 순위 이해하기

그리고 Terraform을 잘 쓰기 위해서는 HCL에 대해 잘 이해하고 활용할 필요가 있겠다. https://github.com/hashicorp/hcl 또는 관련 내용을 찾아보고 충분히 학습하도록 하자.

 

몇 가지 실습을 더 해보았다. 프로바이더 버전을 과하게 높게 설정해보니 해당 프로바이더 버전을 찾을 수 없다는 오류를 확인할 수 있었다.

 

terraform {
  required_version = ">= 1.0.0"
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = ">=10000.0.0"
    }
  }
}

resource "local_file" "abc" {
  content  = "123!"
  filename = "${path.module}/abc.txt"
}

 

 

" >= 2.0.0"으로 수정한 이후 제대로 동작하는 결과를 확인할 수 있었다.

 

이와 같이 프로바이더, 리소스, 종속성, 리소스 속성 참조, 데이터 소스 구성, 데이터 소스 속성 참조, 변수 선언 방식 및 사용 등을 추가로 실습하여 확인해보았다.

 

그리고 변수 입력 방식과 우선순위도 스터디를 진행하였다. 변수를 선언하는 방식에 따라 변수 우선 순위가 있으므로 이를 적절히 사용한다면 로컬 환경과 빌드 서버 환경에서의 정의를 다르게 하거나, 프로비저닝 파이프라인을 구성하는 경우 외부 값을 변수에 저장할 수가 있다.

(출처: https://spacelift.io/blog/terraform-tfvars )

 

1.3. Terraform으로 AWS 네트워크 및 EC2 리소스 관리하기

이렇게 여러 가지 Terraform 기본을 살펴보았다면, 그 다음으로는 AWS에서 VPC 및 서브넷 등 상관관계가 있는 리소스에 대한 생성 및 관리를 Terraform에서 어떻게 이루어지는지 실습을 통해 확인해보는 과정을 진행하였다.

 

먼저 vpc.tf 라는 파일을 생성하여 배포해보자.

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"

  tags = {
    Name = "aews-study"
  }
}

 

# 배포
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
terraform state show aws_vpc.myvpc

# VPC 확인
export AWS_PAGER=""
aws ec2 describe-vpcs | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml

 

그 다음에는 VPC DNS 옵션을 수정한 후 배포를 해보자. 아래 코드에서 7번째 및 8번째 줄이 추가된 것이다.

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "aews-study"
  }
}

 

배포 이전

# 배포
terraform plan && terraform apply -auto-approve

 

배포 이후, DNS hostnames 및 DNS resolution 부분이 enabled 상태로 바뀐 것을 확인할 수 있다.

 

그 다음으로는 서브넷을 2개 추가해보자.

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "aews-study"
  }
}

resource "aws_subnet" "mysubnet1" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.1.0/24"

  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "t101-subnet1"
  }
}

resource "aws_subnet" "mysubnet2" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.2.0/24"

  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "aews-subnet2"
  }
}

output "aws_vpc_id" {
  value = aws_vpc.myvpc.id
}

 

# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc

terraform state show aws_subnet.mysubnet1

terraform output
terraform output aws_vpc_id
terraform output -raw aws_vpc_id

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 서브넷 확인
aws ec2 describe-subnets --output text

# 참고 : aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-<자신의 VPC ID>"
VPCID=$(terraform output -raw aws_vpc_id)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" | jq
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --output table

 

graph.dot 과 같이 생성되는 파일은 Graphviz Interactive Preview와 같은 VSCode extension 을 설치하면 아래 스크린샷과 같이 그래프 그림 형태로 확인할 수 있다.

 

그 다음에는 vpc.tf 코드 내용을 수정하여 IGW 인터넷 게이트웨이를 추가해보자.

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "aews-study"
  }
}

resource "aws_subnet" "mysubnet1" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.1.0/24"

  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "aews-subnet1"
  }
}

resource "aws_subnet" "mysubnet2" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.2.0/24"

  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "aews-subnet2"
  }
}


resource "aws_internet_gateway" "myigw" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "aews-igw"
  }
}

output "aws_vpc_id" {
  value = aws_vpc.myvpc.id
}

 

# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc

 

그 다음에는 vpc.tf 코드를 수정하여 IGW 인터넷 게이트웨이로 전달하는 디폴트 라우팅 정보를 추가하자.

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "aews-study"
  }
}

resource "aws_subnet" "mysubnet1" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.1.0/24"

  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "aews-subnet1"
  }
}

resource "aws_subnet" "mysubnet2" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.2.0/24"

  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "aews-subnet2"
  }
}


resource "aws_internet_gateway" "myigw" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "aews-igw"
  }
}

resource "aws_route_table" "myrt" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "aews-rt"
  }
}

resource "aws_route_table_association" "myrtassociation1" {
  subnet_id      = aws_subnet.mysubnet1.id
  route_table_id = aws_route_table.myrt.id
}

resource "aws_route_table_association" "myrtassociation2" {
  subnet_id      = aws_subnet.mysubnet2.id
  route_table_id = aws_route_table.myrt.id
}

resource "aws_route" "mydefaultroute" {
  route_table_id         = aws_route_table.myrt.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.myigw.id
}

output "aws_vpc_id" {
  value = aws_vpc.myvpc.id
}

 

# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_route.mydefaultroute
aws_route_table.myrt
aws_route_table_association.myrtassociation1
aws_route_table_association.myrtassociation2
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc

terraform state show aws_route.mydefaultroute

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 라우팅 테이블 확인
#aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=aews-rt' --query 'RouteTables[].Associations[].SubnetId'
aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=aews-rt' --output table

 

그 다음에는 보안 그룹과 EC2 배포를 위한 sg.tf, ec2.tf 파일을 생성한다.

 

- sg.tf 파일 내용:

resource "aws_security_group" "mysg" {
  vpc_id      = aws_vpc.myvpc.id
  name        = "AEWS SG"
  description = "AEWS Study SG"
}

resource "aws_security_group_rule" "mysginbound" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

resource "aws_security_group_rule" "mysgoutbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

 

# 배포
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
aws_security_group.mysg
aws_security_group_rule.mysginbound
aws_security_group_rule.mysgoutbound
...

terraform state show aws_security_group.mysg
terraform state show aws_security_group_rule.mysginbound

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

 

- ec2.tf 파일 내용:

data "aws_ami" "my_amazonlinux2" {
  most_recent = true
  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }

  owners = ["amazon"]
}

resource "aws_instance" "myec2" {

  depends_on = [
    aws_internet_gateway.myigw
  ]

  ami                         = data.aws_ami.my_amazonlinux2.id
  associate_public_ip_address = true
  instance_type               = "t2.micro"
  vpc_security_group_ids      = ["${aws_security_group.mysg.id}"]
  subnet_id                   = aws_subnet.mysubnet1.id

  user_data = <<-EOF
              #!/bin/bash
              wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
              mv busybox-x86_64 busybox
              chmod +x busybox
              echo "Web Server</h1>" > index.html
              nohup ./busybox httpd -f -p 80 &
              EOF

  user_data_replace_on_change = true

  tags = {
    Name = "aews-myec2"
  }
}

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

 

# 
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
data.aws_ami.my_amazonlinux2
aws_instance.myec2
...

terraform state show data.aws_ami.my_amazonlinux2
terraform state show aws_instance.myec2

# 데이터소스 값 확인
terraform console
> 
data.aws_ami.my_amazonlinux2.id
"ami-0972fbae82d8513f6"
data.aws_ami.my_amazonlinux2.image_id
data.aws_ami.my_amazonlinux2.name
data.aws_ami.my_amazonlinux2.owners
data.aws_ami.my_amazonlinux2.platform_details
data.aws_ami.my_amazonlinux2.hypervisor
data.aws_ami.my_amazonlinux2.architecture
exit

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 출력된 EC2 퍼블릭IP로 cul 접속 확인
terraform output -raw myec2_public_ip
43.203.247.218

MYIP=$(terraform output -raw myec2_public_ip)
while true; do curl --connect-timeout 1  http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done

 

 

AWS 리소스에 대한 의존성은 다음과 같다.

 

이와 같이 AWS EC2 리소스와 Security Group, 그리고 관련한 네트워크 리소스에 대한 전반적인 의존성을 확보하면서 Terraform으로 생성해보는 실습을 진행하였다. 실습을 다 진행한 후에는 리소스 삭제를 꼭 하자.

 

terraform destroy -auto-approve

 

1.4. 프로바이더 활용하기 - Tier

Terraform는 프로바이더에 대한 Tier (Official, Partner, Community) 가 있다. https://registry.terraform.io/browse/providers 에서는 Official은 Hashicorp에서 직접 유지보수를 하는 Tier, Partner는 기술 파트너를 통해 유지보수가 이루어지는 Tier, Community는 Hashicorp 커뮤니티 생태계를 통해 퍼블리싱 및 유지보수가 이루어진다고 한다.

 

Terraform - Provider Tiers (출처: https://registry.terraform.io/browse/providers )

 

https://registry.terraform.io/providers/hashicorp/aws/latest 링크를 통해 aws provider 버전을 확인해보았다. 현재 5.47.0으로 Hashicorp에서 직접 유지보수하는 Official Tier에 해당하였다.

 

1.5. 프로바이더 활용 및 모듈화

Terraform에서 프로바이더를 잘 활용하기 위한 내용으로 다음을 살펴보았다.

 

  • 로컬 이름과 프로바이더 지정: 같은 이름에 해당하는 프로바이더에 대해 다른 로컬 이름을 부여하여 사용할 수 있다.  다음은 동일한 http 접두사를 사용하는 다수 프로바이더를 적용하는 예시이다.
terraform {
  required_providers {
    architech-http = {
      source = "architect-team/http"
      version = "~> 3.0"
    }
    http = {
      source = "hashicorp/http"
    }
    aws-http = {
      source = "terraform-aws-modules/http"
    }
  }
}

data "http" "example" {
  provider = aws-http
  url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"

  request_headers = {
    Accept = "application/json"
  }
}

 

  • 단일 프로바이더의 다중 정의: 동일한 프로바이더를 사용하지만 다른 조건을 갖는 경우, 사용되는 리소스마다 별도로 선언된 프로바이더를 지정해야 하는 경우가 있다. 아래 예제는 리전을 다르게 구성한 AWS 프로바이더를 aws_instance에 지정하는 방법이다.
provider "aws" {
  region = "ap-southeast-1"
}

provider "aws" {
  alias = "seoul"
  region = "ap-northeast-2"
}

resource "aws_instance" "app_server1" {
  ami           = "ami-06b79cf2aee0d5c92"
  instance_type = "t2.micro"
}

resource "aws_instance" "app_server2" {
  provider      = aws.seoul
  ami           = "ami-0ea4d4b8dc1e46212"
  instance_type = "t2.micro"
}

 

#
terraform init && terraform plan
terraform apply -auto-approve

#
terraform state list
echo "aws_instance.app_server1.public_ip" | terraform console
echo "aws_instance.app_server2.public_ip" | terraform console
aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table
aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table --region ap-southeast-1

# 삭제
terraform destroy -auto-approve

 

  • 프로바이더 요구사항 정의: 프로바이더 요구사항은 terraform 블록의 required_providers 블록에 여러 개를 정의할 수 있다. 다음과 같은 형식으로 요구사항 정의 블록을 사용하며, 호스트 주소, 네임스페이스, 유형 파트를 참고하자.
terraform {
  required_providers {
    <프로바이더 로컬 이름> = {
      source = [<호스트 주소>/]<네임스페이스>/<유형>
      version = <버전 제약>
    }
    ...
  }
}

1.5. 모듈

시간이 지날수록 구성이 복잡해지고 관리하는 리소스가 늘어나게 되기에 모듈화를 통해 관리하는 것이 좋다. 하나의 프로비저닝에서 사용자와 패스워드를 여러 번 구성해야 하는 경우를 가상의 시나리오로 삼아 모듈화를 진행하는 실습을 진행해보았다.

  • random_pet는 이름을 자동으로 생성하고, random_password는 사용자의 패스워드를 설정한다 - random_pet
  • random_password는 random 프로바이더 리소스로 난수 형태로 패스워드를 만들 수 있다.

먼저 terraform-random-pwgen 모듈을 만들어보자. 디렉터리를 하나 새로 만들고 06-module-traning/modules/terraform-random-pwgen/main.tf variable.tf output.tf 파일을 만든다.

 

mkdir -p 06-module-traning/modules/terraform-random-pwgen

 

# main.tf
resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}
# variable.tf
variable "isDB" {
  type        = bool
  default     = false
  description = "패스워드 대상의 DB 여부"
}
# output.tf
output "id" {
  value = random_pet.name.id
}

output "pw" {
  value = nonsensitive(random_password.password.result) 
}

 

#
cd 06-module-traning/modules/terraform-random-pwgen

#
ls *.tf
terraform init && terraform plan

# 테스트를 위해 apply 시 변수 지정
terraform apply -auto-approve -var=isDB=true
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
id = "knowing-aardvark"
pw = "Y5eeP0i2KLLE9gBa"

# 확인
terraform state list
terraform state show random_pet.name
terraform state show random_password.password

# graph 확인
terraform graph > graph.dot

 

 

자식 모듈 호출을 실습해본다. 다수 리소스를 같은 목적으로 여러 번 반복해서 사용하려면 리소스 수만큼 반복해 구성 파일을 정의해야 하고 이름도 고유하게 설정해줘야 하는 부담이 있지만, 모듈을 활용하면 반복되는 리소스 묶음을 최소화할 수 있다.

 

mkdir -p 06-module-traning/06-01-basic

 

# 06-01-basic/main.tf

module "mypw1" {
  source = "../modules/terraform-random-pwgen"
}

module "mypw2" {
  source = "../modules/terraform-random-pwgen"
  isDB   = true
}

output "mypw1" {
  value  = module.mypw1
}

output "mypw2" {
  value  = module.mypw2
}

 

#
cd 06-module-traning/06-01-basic

#
terraform init && terraform plan && terraform apply -auto-approve
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
mypw1 = {
  "id" = "on-mammal"
  "pw" = "3EzelOixyR"
}
mypw2 = {
  "id" = "brave-bull"
  "pw" = "KS%jBcA*dpI$p6*0"
}

# 확인
terraform state list

# tfstate에 모듈 정보 확인
cat terraform.tfstate | grep module

# terraform init 시 생성되는 modules.json 파일 확인
tree .terraform
.terraform
├── modules
│   └── modules.json
...

## 모듈로 묶여진 리소스는 module이라는 정의를 통해 단순하게 재활용하고 반복 사용할 수 있다.
## 모듈의 결과 참조 형식은 module.<모듈 이름>.<output 이름>으로 정의된다.
cat .terraform/modules/modules.json | jq
{
  "Modules": [
    {
      "Key": "",
      "Source": "",
      "Dir": "."
    },
    {
      "Key": "mypw1",
      "Source": "../modules/terraform-random-pwgen",
      "Dir": "../modules/terraform-random-pwgen"
    },
    {
      "Key": "mypw2",
      "Source": "../modules/terraform-random-pwgen",
      "Dir": "../modules/terraform-random-pwgen"
    }
  ]
}

# graph 확인
terraform graph > graph.dot

 

2. Terraform으로 EKS 배포

첫 번째 EKS 클러스터를 배포해보자.  EKS Module (링크: https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/20.8.5 )를 사용한다.

# 코드 가져오기
git clone https://github.com/gasida/aews-cicd.git
cd aews-cicd/4

# terraform 환경 변수 저장
export TF_VAR_KeyName=[각자 ssh keypair]
export TF_VAR_KeyName='kp-ian'
echo $TF_VAR_KeyName

# 
terraform init
terraform plan

# 10분 후 배포 완료
terraform apply -auto-approve

 

 

약 10분이 지나 배포가 완료되면 배포 정보를 확인한다. kubectl 명령어를 실행 가능하도록  인증 정보를 가져오는 단계가 필요하다.

#
kubectl get node -v=6

# EKS 클러스터 인증 정보 업데이트
CLUSTER_NAME=myeks
aws eks update-kubeconfig --region ap-northeast-2 --name $CLUSTER_NAME
kubectl config rename-context "arn:aws:eks:ap-northeast-2:$(aws sts get-caller-identity --query 'Account' --output text):cluster/$CLUSTER_NAME" "Aews-Labs"

#
kubectl cluster-info
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -A

 

 

기타 설치도 진행하자.

 

# ExternalDNS
MyDomain=<자신의 도메인>
MyDomain=sdndev.link
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# gp3 스토리지 클래스 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/PKOS/main/aews/gp3-sc.yaml

 

Kube Ops까지 잘 나오는 것을 확인하였다.

 

그 다음 코드를 재사용하여 두 번째 클러스터를 배포해보자. 다른 클러스터에 접근하므로 서로 다른 kubeconfig 가 필요하다. myeks 클러스터에 접근할 때는 디폴트 config를, myeks2 클러스터에 접근할 때는 myeks2config 파일을 참조하는 것으로 확인을 하였다.

#
cd ..
mkdir 5
cd 5
cp ../4/*.tf .
ls

#
terraform init
terraform apply -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"

# EKS 클러스터 인증 정보 가져오기
CLUSTER_NAME2=myeks2
aws eks update-kubeconfig --region ap-northeast-2 --name $CLUSTER_NAME2 --kubeconfig ./myeks2config

# EKS 클러스터 정보 확인
kubectl --kubeconfig ./myeks2config get node 
kubectl --kubeconfig ./myeks2config get pod -A

 

 

실습을 다 하였다면 첫 번째 클러스터 및 두 번째 클러스터를 아래를 참고하여 완전히 삭제하도록 하자. 첫 번째 클러스터를 삭제하고

# CLB는 terraform으로 배포하지 않고 직접 수동으로 배포되었으니, 수동으로 삭제를 해주어야 함
helm uninstall kube-ops-view --namespace kube-system

# 클러스터 삭제
terraform destroy -auto-approve

 

두 번째 클러스터도 삭제한다.

# 클러스터 삭제
terraform destroy -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"

 

 

 

 

본 게시물은 CloudNet@에서 진행하는 AEWS (Amazon EKS Workshop Study) 스터디에 참여하며 정리한 내용입니다.

 

0. 실습 환경 배포 & 소개

이번 시간 역시 원클릭 배포를 먼저 진행한다. 작업용 EC2 IP 주소 확인이 가능한 때 접속한 후, 이후 EKS 배포가 완료되기를 약 20분 정도 기다리자.

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick6.yaml

# CloudFormation 스택 배포
aws cloudformation deploy --template-file eks-oneclick6.yaml --stack-name myeks --parameter-overrides KeyName=kp-ian SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=`cat ~/.aws/credentials | grep aws_access_key_id | awk '{print $3}'` MyIamUserSecretAccessKey=`cat ~/.aws/credentials | grep aws_secret_access_key | awk '{print $3}'` ClusterBaseName=myeks --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/kp-ian.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
or
ssh -i ~/.ssh/kp-ian.pem root@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
~ password: qwe123

 

그 다음 기본 설정을 진행한다.

# default 네임스페이스 적용
kubectl ns default

# 노드 정보 확인 : t3.medium
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone

# ExternalDNS
MyDomain=sdndev.link
echo "export MyDomain=sdndev.link" >> /etc/profile
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# gp3 스토리지 클래스 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/PKOS/main/aews/gp3-sc.yaml

# 노드 보안그룹 ID 확인
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

 

프로메테우스와 그라파나도 미리 설치하도록 하자.

# 사용 리전의 인증서 ARN 확인
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN

# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# 파라미터 파일 생성 : PV/PVC(AWS EBS) 삭제에 불편하니, 4주차 실습과 다르게 PV/PVC 미사용
cat <<EOT > monitor-values.yaml
prometheus:
  prometheusSpec:
    podMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelectorNilUsesHelmValues: false
    retention: 5d
    retentionSize: "10GiB"

  ingress:
    enabled: true
    ingressClassName: alb
    hosts: 
      - prometheus.$MyDomain
    paths: 
      - /*
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/success-codes: 200-399
      alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: '443'

grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator
  defaultDashboardsEnabled: false

  ingress:
    enabled: true
    ingressClassName: alb
    hosts: 
      - grafana.$MyDomain
    paths: 
      - /*
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/success-codes: 200-399
      alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: '443'

alertmanager:
  enabled: false
EOT
cat monitor-values.yaml | yh

# 배포
kubectl create ns monitoring
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 57.2.0 \
--set prometheus.prometheusSpec.scrapeInterval='15s' --set prometheus.prometheusSpec.evaluationInterval='15s' \
-f monitor-values.yaml --namespace monitoring

# Metrics-server 배포
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# 프로메테우스 ingress 도메인으로 웹 접속
echo -e "Prometheus Web URL = https://prometheus.$MyDomain"

# 그라파나 웹 접속 : 기본 계정 - admin / prom-operator
echo -e "Grafana Web URL = https://grafana.$MyDomain"

 

1. Docker

도커 허브 (https://hub.docker.com)에 회원 가입되지 않았다면 회원 가입을 먼저 진행한다.

도커 허브 (hub.docker.com) 웹 페이지

 

# ubuntu 이미지 다운로드
docker pull ubuntu:20.04
docker images

# 실습을 위한 디렉터리 생성 및 이동
mkdir -p /root/myweb && cd /root/myweb

# Dockerfile 파일 생성
vi Dockerfile
FROM ubuntu:20.04
ENV TZ=Asia/Seoul VERSION=1.0.0 NICK=sdndev
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
    sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
    sed -i 's/security.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
    apt-get update && apt-get install -y apache2 figlet && \
    echo "$NICK Web Server $VERSION<br>" > /var/www/html/index.html && \
    echo "<pre>" >> /var/www/html/index.html && \
    figlet AEWS Study >> /var/www/html/index.html && \
    echo "</pre>" >> /var/www/html/index.html
EXPOSE 80
CMD ["usr/sbin/apache2ctl", "-DFOREGROUND"]

# 이미지 빌드
cat Dockerfile
docker build -t myweb:v1.0.0 .
docker images
docker image history myweb:v1.0.0
docker image inspect myweb:v1.0.0 | jq

# 컨테이너 실행
docker run -d -p 80:80 --rm --name myweb myweb:v1.0.0
docker ps
curl localhost

# 웹 접속 확인
curl -s ipinfo.io/ip | awk '{ print "myweb = http://"$1"" }'

 

Docker 이미지 빌드가 잘 되고 컨테이너 이미지 실행 및 로컬 & 웹 접속이 잘 되는 것을 확인할 수 있었다.

 

(빌드한 Docker 이미지 실행 및 로컬 & 웹 접속 확인 완료!)

 

그 다음 도커 허브에 업로드해본다.

#
#DHUB=<도커 허브 계정>
DHUB=ianychoi
docker tag myweb:v1.0.0 $DHUB/myweb:v1.0.0
docker images

# 도커 허브 로그인
docker login
Username: <자신의 ID>
Password: <암호>
## 로그인 정보는 /[계정명]/.docker/config.json 에 저장됨. docker logout 시 삭제됨
## cat /root/.docker/config.json | jq

# push 로 이미지를 저장소에 업로드
docker push $DHUB/myweb:v1.0.0

 

도커 허브에서 업로드된 이미지 확인을 완료하였다.

 

 

해당 저장소 이미지를 활용해본다.

# 컨테이너 종료
docker rm -f myweb
docker ps

# 로컬 이미지 삭제
docker rmi $DHUB/myweb:v1.0.0
docker images

# 
docker run -d -p 80:80 --rm --name myweb $DHUB/myweb:v1.0.0
docker images

# 확인
docker ps
curl localhost
curl -s ipinfo.io/ip | awk '{ print "myweb = http://"$1"" }'

# 삭제
docker rm -f myweb

 

로컬에 이미지가 없는 상황에서 도커 허브로부터 이미지를 가져와 잘 실행됨을 확인하였다.

2. Jenkins

CI/CD 툴인 Jenkins를 EC2 Bastion VM에 설치해보자.

# 실습 편리를 위해서 root 계정 전환
sudo su -

# Add required dependencies for the jenkins package
# https://docs.aws.amazon.com/corretto/latest/corretto-17-ug/amazon-linux-install.html
sudo yum install fontconfig java-17-amazon-corretto -y
java -version
alternatives --display java
JAVA_HOME=/usr/lib/jvm/java-17-amazon-corretto.x86_64
echo $JAVA_HOME

# 젠킨스 설치
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
sudo yum upgrade
sudo yum install jenkins -y
sudo systemctl daemon-reload
sudo systemctl enable jenkins && sudo systemctl start jenkins
sudo systemctl status jenkins

# 초기 암호 확인
sudo systemctl status jenkins
cat /var/lib/jenkins/secrets/initialAdminPassword

# 접속 주소 확인 
curl -s ipinfo.io/ip | awk '{ print "Jenkins = http://"$1":8080" }'
Jenkins = http://43.202.2.88:8080

 

 

설치가 완료된 다음에는 몇 가지 세팅을 진행한다. 먼저 초기 암호를 입력한다. 그 다음 제안 플러그인을 설치하자. "Install suggested plugins"를 클릭한다.

 

 

다음과 같이 플러그인 설치가 이루어진다.

 

다만 본인은 설치할 때 미러링 서버에 이슈가 있어서 그런지 아래 스크린샷과 같이 잘 설치가 되지 않았다. 이를 해결하고자 다른 리눅스 환경에서 동일한 Jenkins를 설치하고 플러그인 설치를 모두 완료한 상태에서 /var/lib/jenkins 폴더 전체를 tar 압축 후 가져오는 형태로 해결을 하였다.

 

플러그인 설치가 잘 되면 그 다음 사용자를 생성하고 Jenkins를 사용하면 된다.

 

Jenkins 관리에서 First-Project를 생성을 해볼 것이다. 그 전에 먼저 "Manage Jenkins" -> "Tools" -> "JDK installations" 부분에 "Add JDK"를 선택 후 아래 스크린샷과 같이 jdk-17, /usr/lib/jvm/java-17-amazon-corretto.x86_64 값을 각각 입력 후 "Save" 버튼을 클릭한다.

 

그 다음에 왼쪽 메뉴에서 "New Item"을 클릭하면 새로운 아이템을 입력하는 화면이 나타난다. "First-Project"라고 이름을 적고 "Freestyle project"를 선택한 다음 하단 "OK"를 클릭하자.

 

그 다음 나타나는 Configuration 메뉴에서는 세부 구성을 설정할 수가 있다. 그 중 "Build Steps"를 클릭 후 "Add build step" -> "Execute shell"을 선택한 다음, echo "Aws Workshop Study" 와 같이 간단한 문장을 출력하도록 입력한 다음에 하단 "Apply" 및 "Save" 버튼을 클릭하여 저장한다.

 

왼쪽 메뉴 중 "Build Now"를 선택하면 실행된 결과가 왼쪽 하단 "Build History"에 보이며 콘솔 출력 결과 또한 확인이 가능하다.

 

다시 Configuration -> Configure -> Build Environment -> Build Steps에서 "java -version", "whoami", "touch hello.txt"를 추가하고 Apply & Save를 클릭한 다음에 "Build Now"를 클릭하여 실행을 해보자. 콘솔에서도 확인할 수 있으며 특히 "tree /var/lib/jenkins/workspace/First-Project" 명령어를 Bastion EC2에서 실행하면 hello.txt 파일이 /var/lib/jenkins 내 workspace 에서 프로젝트 명 내에 파일이 저장된다는 것을 확인할 수 있다.

 

그 다음으로는 Jenkins에서 Docker를 사용하도록 설정해본다.

# jenkins 유저로 docker 사용 가능하게 설정
grep -i jenkins /etc/passwd
usermod -s /bin/bash jenkins
grep -i jenkins /etc/passwd

# jenkins 유저 전환
su - jenkins
whoami
pwd
docker info
exit

#
chmod 666 /var/run/docker.sock
usermod -aG docker jenkins

# Jeknins 유저로 확인
su - jenkins
docker info

# Dockerhub로 로그인 하기
docker login
Username: <자신의 계정명>
Password: <자신의 암호>

# myweb:v2.0.0 컨테이너 이미지 생성을 위한 Dockerfile 준비
# 실습을 위한 디렉터리 생성 및 이동
mkdir -p ~/myweb2 && cd ~/myweb2

# Dockerfile 파일 생성
vi Dockerfile
FROM ubuntu:20.04
ENV TZ=Asia/Seoul VERSION=2.0.0 NICK=sdndev
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
    sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
    sed -i 's/security.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
    apt-get update && apt-get install -y apache2 figlet && \
    echo "$NICK Web Server $VERSION<br>" > /var/www/html/index.html && \
    echo "<pre>" >> /var/www/html/index.html && \
    figlet AEWS Study >> /var/www/html/index.html && \
    echo "</pre>" >> /var/www/html/index.html
EXPOSE 80
CMD ["usr/sbin/apache2ctl", "-DFOREGROUND"]

# 모니터링
watch -d 'docker images; echo; docker ps'

-----------
# (참고) 이미지 빌드
docker build -t myweb:v2.0.0 -f /var/lib/jenkins/myweb2/Dockerfile

# (참고) 컨테이너 실행
docker run -d -p 80:80 --rm --name myweb myweb:v2.0.0

 

jenkins 사용자에 대해서는 디폴트 쉘이 /bin/false로 되어 있기에 이를 /bin/bash로 변경한 다음 su 명령어를 통해 jenkins 사용자로 shell을 실행하였다. 그런데 docker info를 실행하면 "permission denied"가 나타나는 것을 확인할 수 있다.

 

이를 위해서는 /var/run/docker.sock 파일에 대해 jenkins 사용자도 액세스할 수가 있어야 한다. 이를 재설정하고 다시 shell에 접속하여 docker info를 실행하면 잘 실행될 것이다.

 

나머지 명령어도 실행하여 Dockerfile 을 만들고 컨테이너가 구동되는지 확인하기 위한 모니터링 준비를 하자.

 

그 다음 다시 Jenkins로 돌아가 Build Step 2개를 추가한 다음 Apply & Save를 클릭한다. 각각은 다음과 같다.

 

cd /var/lib/jenkins/myweb2
docker build -t myweb:v2.0.0 .

 

docker run -d -p 80:80 --rm --name myweb myweb:v2.0.0

 

 

"Build Now"를 해보자. 모니터링하던 부분에서 다음과 같은 변화를 발견할 수 있었다.

 

 

"docker images", "docker ps", "curl localhost"와 같은 명령어로 테스트해보자.

 

완료한 다음에는 실습 리소스를 삭제한다.

 

docker rm -f myweb
docker rmi myweb:v2.0.0

 

이제 GitHub 저장소와 연동하는 부분을 실습해보자.

https://github.com/gasida/aews-cicd 를 fork한 다음, fork가 된 저장소를 가지고 실습을 진행할 예정이다.

 

Jenkins에서 Dashboard -> New Item을 클릭하고 "Trigger-Project" 이름으로 "Freestyle project"를 생성하도록 한다.

 

그리고 "This project is parameterized" 옵션을 선택하면 "Add parameter" 버튼을 통해 파라미터를 추가할 수 있다. "String parameter"로 VERSION에 대해서는 v1.0.0 값을, NICK 이름에 대해서는 본인의 닉네임 값을 추가한다.

 

그 다음, Source Code Management에서 "Git"를 선택하고 저장소 URL을 지정, branch는 "*/main"으로 (master 대신 main으로 변경) 설정한 다음, "Additional Behaviours"에서 "Sparse Checkout paths"를 추가, Path 값은 1로 설정한다.

 

또한 "Build Triggers"에서 "Poll SCM"을 클릭한 다음, Schedule에 "* * * * *" 값을 입력하자.

 

Build Steps에는 "Execute shell" 2개를 추가한다. 아래 내용을 참고하여 추가하도록 하자.

cd /var/lib/jenkins/myweb2
rm -rf Dockerfile
wget https://raw.githubusercontent.com/$NICK/aews-cicd/main/1/Dockerfile
docker build -t myweb:$VERSION .
docker run -d -p 80:80 --rm --name myweb myweb:$VERSION

 

 

여기까지 다 잘 했으면 "Apply" & "Save" 버튼을 클릭한다.

 

이제 GitHub 저장소에서 Trigger를 발생시켜본다. GitHub 저장소 내 1/Dockerfile 에 대해 버전 정보와 닉네임을 편집하여 커밋을 만들어보자.

 

약 1분 정도 이후에 Jenkins에서 확인해보면 오류가 발생한 상황이 보인다. 콘솔에서 세부 내용을 살펴보자.

 

최신 커밋에 따른 파일 내용은 잘 가져왔는데 경로 등이 맞지 않는 것인지 실행이 잘 안되는 것 같다. 다른 "Execute shell"에 있다면 경로가 다를 수 있기에 "cd /var/lib/jenkins/myweb2"를 추가하고 "Build with Parameters" 버튼을 클릭하여 재실행해보았다.

 

확인해보니 실행은 잘 되었다. 그런데 버전이 다른 것을 확인할 수 있다.

이 부분에서 소스 코드가 우선인지 Jenkins에 있는 파라미터가 우선인지 잠깐 생각해볼 필요가 있겠다. CI/CD 전반적인 관점에서는 소스 코드가 우선되어야 할 것 같으나, 반면 Jenkins 입장에서는 파라미터에 있는 값을 활용하는 것이 우선시되어야한다고 볼 수도 있을 것이다. Jenkins 파라미터를 우선으로 할 경우에는 아래 스크린샷처럼 sed를 활용해 값을 바꾸어주고 그 결과를 확인해보자.

sed -i "s/VERSION=[0-9.]*/VERSION=${VERSION}/g" Dockerfile
sed -i "s/NICK=[A-Za-z0-9.]*/NICK=${NICK}/g" Dockerfile

 

이렇게 해도 오류가 발생하였는데, 기존 Docker 프로세스를 종료하지 않아서이다. 따라서 "docker rm -f myweb"을 Execute shell 에 추가하는 것으로 해결하였다.

 

 

(최종 성공 스크린샷)

아래 명령어를 실행하여 실습 내용을 정리하도록 하자.

docker rm -f myweb
docker rmi myweb:v1.9.9
docker rmi myweb:v1.0.0

 

그 다음으로는 Jenkins 파이프라인을 실습해보도록 하자.

 

Dashboard -> "New Item"을 클릭한 다음에 "First-Pipeline" 이름을 갖는 "Pipeline"을 생성한다.

 

파이프라인 스크립트에 아래 내용을 입력하여 테스트해보도록 하자.

pipeline {
    agent any

    stages {
        stage('정보 확인') {
            steps {
                echo 'Hello World'
                sh 'java -version'
            }
        }
        stage('가라 배포') {
            steps {
                echo "Deployed successfully!";
            }
        }
    }
}

이후 몇 가지 변경 및 실습을 추가로 진행해보았다. Maven의 경우 tools 내 이름이 "Manage Jenkins" -> Tools 내 설정된 이름과 동일해야 한다.

뿐만 아니라 "Pipeline Syntax" 기능을 사용하여 파이프라인 스크립트를 손쉽게 만들 수도 있다. "sh: Shell Script"로 두 줄 명령어에 대해 스크립트를 요청하니 자동으로 작은 따옴표 3개로 감싸주었다.

 

3. Jenkins with Kubernetes

이제 Jenkins를 쿠버네티스 서비스인 EKS 환경과 연동을 시켜보자.

# jenkins 사용자에서 아래 작업 진행
whoami
mkdir ~/.kube

# root 계정에서 아래 복사 실행
cp ~/.kube/config /var/lib/jenkins/.kube/config
chown jenkins:jenkins /var/lib/jenkins/.kube/config

# jenkins 사용자에서 aws eks 사용(sts 호출 등)을 위한 자격증명 설정
aws configure
AWS Access Key ID [None]: AKIA5ILF2###
AWS Secret Access Key [None]: ###
Default region name [None]: ap-northeast-2

# jenkins 사용자에서 kubectl 명령어 사용 확인
kubectl get pods -A

 

파이프라인으로 디플로이먼트/서비스를 배포해보고자 한다.

 

진행하기 위해서는 fork한 GitHub 리포지터리에 3/deploy/deployment-svc.yaml 파일이 있는데 이 파일에서 image 부분에 자신의 도커 허브 이미지를 가리키도록 수정을 할 필요가 있다.

 

이제 Jenkins에서 새로운 Item을 만들어보자. "k8s-1" 이으로 Pipeline을 생성한다. 아래 내용으로 구성한다.

pipeline {
    agent any

    tools {
        jdk 'jdk-17'
    }

    environment {
        DOCKERHUB_USERNAME = 'ianychoi'
        GITHUB_URL = 'https://github.com/ianychoi/aews-cicd.git'
        // deployment-svc.yaml -> image: ianychoi/myweb:v1.0.0        
        DIR_NUM = '3'
    }

    stages {
        stage('Container Build') {
            steps {	
                // 릴리즈파일 체크아웃
                checkout scmGit(branches: [[name: '*/main']], 
                    extensions: [[$class: 'SparseCheckoutPaths', 
                    sparseCheckoutPaths: [[path: "/${DIR_NUM}"]]]], 
                    userRemoteConfigs: [[url: "${GITHUB_URL}"]])

                // 컨테이너 빌드 및 업로드
                sh "docker build -t ${DOCKERHUB_USERNAME}/myweb:v1.0.0 ./${DIR_NUM}"
                sh "docker push ${DOCKERHUB_USERNAME}/myweb:v1.0.0"
            }
        }

        stage('K8S Deploy') {
            steps {
                sh "kubectl apply -f ./${DIR_NUM}/deploy/deployment-svc.yaml"
            }
        }
    }
}

 

모니터링을 위해 준비하고

watch -d kubectl get pod,svc,ep

 

접속 테스트용 파드를 생성한다.

 

# 배포
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: netpod
  labels:
    app: pod
spec:
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

실제 잘 동작하는지 아래 명령어를 참고하여 확인해보자.

#
kubectl exec -it netpod -- curl myweb:8080
kubectl exec -it netpod -- curl myweb:8080 | grep Web
while true; do kubectl exec -it netpod -- curl myweb:8080 | grep Web; echo; done

# 작업공간 확인
tree /var/lib/jenkins/workspace/k8s-1
cat /var/lib/jenkins/workspace/k8s-1/3/Dockerfile

 

실행 결과 처음에는 실패하였는데 Jenkins 사용자에 대한 Shell에서 "aws configure"로 구성을 제대로 하지 않았기 때문이었다. 이를 제대로 구성한 후 다시 실행하여 잘 실행됨을 확인할 수 있었다.

 

 

실습 완료 후에는 자원을 삭제한다.

kubectl delete deploy,svc myweb

 

4. Argo

Argo를 설치해본다.

# helm 설치
cat <<EOT > argocd-values.yaml
global:
  domain: argocd.$MyDomain

configs:
  params:
    server.insecure: true

controller:
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true

server:
  ingress:
    enabled: true
    controller: aws
    ingressClassName: alb
    hostname: "argocd.$MyDomain"
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/backend-protocol: HTTP
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      #alb.ingress.kubernetes.io/success-codes: 200-399
      #alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
      #alb.ingress.kubernetes.io/group.name: study
      alb.ingress.kubernetes.io/ssl-redirect: '443'
    aws:
      serviceType: ClusterIP
      backendProtocolVersion: GRPC
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true

repoServer:
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true

applicationSet:
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true

notifications:
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true
EOT

kubectl create ns argocd
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 6.7.11 -f argocd-values.yaml --namespace argocd

# 확인
kubectl get ingress,pod,svc -n argocd
kubectl get crd | grep argo
applications.argoproj.io                     2024-04-14T11:16:14Z
applicationsets.argoproj.io                  2024-04-14T11:16:14Z
appprojects.argoproj.io                      2024-04-14T11:16:14Z

# 최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
t-SGogfJOsiaMFd8

 

 

이후 "NEW APP"을 클릭하여 새 앱을 생성한다.

 

 

생성하면 바로 "OutOfSync" 상태로 변하는 것을 확인할 수 있다.

 

"Sync" -> "Synchronize" 버튼을 클릭한다.

 

ArgoCD는 Desired Manifest와 비교하여 Sync가 이루어진 후에 Live Manifest 결과로 나타난다. myweb에 대한 Deploy를 선택하고, Live Manifest에서 Edit 버튼을 클릭한 다음 "add: label-test"를 아래 스크린샷과 같이 추가해보자.

 

 

아래와 같이 "watch -d kubectl get deploy -n first --show-labels"를 통해 모니터링해보면 Save를 하면 라벨이 추가된 결과를 확인할 수 있다.

 

 

"kubectl edit deploy -n first myweb"을 통해 확인하면 "add: label-test"가 있는 것을 확인할 수 있다.

 

그런데 ArgoCD에서는 diff결과가 나타나지 않는다. 심지어 "add2=k8s-add"와 같은 것을 추가해도 diff 결과가 없다.

 

반면 소스 코드에서 "gitadd: webedit"와 같은 라벨을 추가해보면 약 3분이 지나서 ArgoCD에서 확인하거나 또는 "Refresh" 버튼을 클릭하여 확인할 수 있다.

 

이 부분은 diff에서 차이를 확인할 수 있다.

 

반영을 하기 위해 "Sync" 버튼을 클릭한다.

 

해당 상황은 이전에 Jenkins를 실습할 때 살펴보았던 상황과 비슷하게 소스 코드를 우선으로 GitOps가 되어야 한다는 철학이 이유가 되지 않을까 싶다.

 

이번에는 Replica 수를 2에서 4로 변경해보자. 역시 마찬가지로 "Refresh" 한 다음 "Sync" -> "Synchronize" 버튼을 클릭하였다.

 

 

ArgoCD에서 삭제 또한 편리하게 가능하다고 한다. "Delete" 버튼을 클릭한 후 앱 이름을 한 번 어 입력하고 "OK" 버튼을 클릭해보자.

 

중요한 결론으로는 "GitOps를 하려거든 대상(k8s)에서 변경하지 말고, 소스(git)에서 변경하자!"가 있겠다.

 

ArgoCD CLI 버전도 있다. 설치하여 확인해보자.

#
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm -f argocd-linux-amd64

#
argocd version

#
argocd login argocd.$MyDomain
Username: admin
Password: ###
'admin:login' logged in successfully

#
kubectl config get-contexts -o name
aewsian@myeks.ap-northeast-2.eksctl.io
argocd cluster add aewsian@myeks.ap-northeast-2.eksctl.io
y 입력

#
argocd app list
NAME  CLUSTER  NAMESPACE  PROJECT  STATUS  HEALTH  SYNCPOLICY  CONDITIONS  REPO  PATH  TARGET

 

앱 생성에 대해 아래와 같이 명령어로 가능하다.

#
kubectl config set-context --current --namespace=argocd
argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-server https://kubernetes.default.svc --dest-namespace default

#
argocd app list
NAME              CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                 PATH       TARGET
argocd/guestbook  https://kubernetes.default.svc  default    default  OutOfSync  Missing  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook

 

또한 Sync도 가능하다.

#
argocd app get guestbook
...

# 모니터링
watch -d kubectl get pod,svc,ep

#
argocd app sync guestbook

 

ArgoCD로 배포한 앱을 삭제해보자.

argocd app delete guestbook
Are you sure you want to delete 'guestbook' and all its resources? [y/n] y

# ns default 로 변경
kubectl ns default

 

Argo Rollouts이란 것도 있다. Argo Rollouts는 Kubernetes에서 Blue-Green 및 Canary 배포 전략 등 고급 deployment 전략을 제공하는 Kubernetes Controller라고 한다.

 

Argo Rollouts를 설치해보자. Helm 차트로 설치해본다. (관련 내용: 링크)

#
cat <<EOT > argorollouts-values.yaml
dashboard:
  enabled: true
  ingress:
    enabled: true
    ingressClassName: alb
    hosts:
      - argorollouts.$MyDomain
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/backend-protocol: HTTP
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":80}, {"HTTPS":443}]'
      alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
      alb.ingress.kubernetes.io/ssl-redirect: '443'
EOT

kubectl create ns argo-rollouts
helm install argo-rollouts argo/argo-rollouts --version 2.35.1 -f argorollouts-values.yaml --namespace argo-rollouts

# 확인
kubectl get all -n argo-rollouts
kubectl get crd | grep argo

 

Argo Rollouts CLI도 설치하자.

#
curl -LO https://github.com/argoproj/argo-rollouts/releases/download/v1.6.4/kubectl-argo-rollouts-linux-amd64
chmod +x ./kubectl-argo-rollouts-linux-amd64
mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

# 설치 확인
kubectl argo rollouts version

 

Canary에 대해 실습을 진행해본다. 다음 전략에 해당한다.

spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 20
      - pause: {}
      - setWeight: 40
      - pause: {duration: 10}
      - setWeight: 60
      - pause: {duration: 10}
      - setWeight: 80
      - pause: {duration: 10}

 

Rollout 및 서비스를 배포해보자.

# Run the following command to deploy the initial Rollout and Service:
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/rollout.yaml
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/service.yaml

 

 

현재 blue에 해당하는 5개 파드가 실행 중인 상황에 있다. 이제 yellow 버전에 대해 rollout을 동작시켜보도록 하자.

 

kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow

 

20%가 진행되었고 멈추어 있는 단계이다. 웹 페이지에서 "Promote" 버튼을 클릭하거나 CLI에서 "kubectl argo rollouts promote rollouts-demo"라고 입력해도 된다. 그러면 다음 과정이 진행되었다가 10초 후 멈추고 그 다음 과정을 계속 진행하는 식으로 동작할 것이다.

 

이번 역시 실습 완료 후에 자원을 삭제하는 것을 꼭 잊지 말도록 하자.

eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts