본 게시물은 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