[EKS@CloudNet] EKS CI/CD

2024. 4. 21. 07:29EKS@CloudNet

- CloudNet에서 주관하는 EKS 스터디 2기 내용입니다

- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다

- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!

 

0. Setup

이번 세션에서는 CI/CD를 위한 툴들을 실습했다. 우선 CI/CD에 대해 간단히 짚고 넘어가면

- CI: Continuous Integration. 지속적인 개발과 기존 코드로의 통합

- CD: Continuous Deployment. 한마디로 자동 배포

 

로 비슷해 보이지만 다르다. 

 

 

실습 세팅은 앞에서 여러 번 했으니 지난 블로그 참고! 

kube-ops-view, prometheus, Grafana를 설치한다.

 

 

 

 

 

 

 

 

 

 

1. Docker & Dockerhub

- 실습을 위해서 우선 도커 회원가입이 필요하다. 

 

# 현재 가지고있는 도커 이미지 확인
docker images

# 우분투 이미지 다운로드
docker pull ubuntu:20.04

# 실습용 디렉토리 생성 및 이동
mkdir -p /root/myweb && cd /root/myweb

 

- 가시다님이 제공해주신 도커파일로 이미지를 빌드한다. 

vi Dockerfile
FROM ubuntu:20.04
ENV TZ=Asia/Seoul VERSION=1.0.0 NICK=<자신의 닉네임>
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"]

# 이미지 빌드하고 태그 추가
docker build -t myweb:v1.0.0 .

# 이미지 Inspection
docker image inspect myweb:v1.0.0 | jq

 

이 도커파일의 내용을 간단하게 설명하면 

    - 베이스 이미지를 우분투 20.04로 해서

    - 환경변수 설정: TZ(Timezone), Version(버전), Nick(닉네임) 을 환경변수로 갖는다

    - 우분투 패키지 관리자와 보안 업데이트를 공식 저장소가 아닌 카카오 미러에서 가져오게 변경한다

    - 업데이트후 아파치 & figlet 실행

    - HTML 문서에 <pre> 태그로 감싸진 아스키 아트를 넣는다

    - 80 포트에서 수신 대기

    - 컨테이너 시작 시 아파치 웹서버가 foreground에서 실행되는 명령을 넣는다

- docker images 로 조회했을 때 새 이미지가 생긴 것을 확인한다

 

- 컨테이너를 실행하고 웹 접속이 되는지 확인한다. <pre> 태그로 감싸진 AEWS Study 아스키아트가 출력되는 것을 볼 수 있다.

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

# 돌고 있는 프로세스 확인
docker ps

# 아스키아트 출력
curl localhost

- 도커파일을 살짝 변경해서 닉네임을 출력해 보았다. Just for fun

- 그러면 이제 이 이미지를 도커허브에 업로드 해보자.

# 이미지 새로 태그
DHUB=hitherex
docker tag myweb:v1.0.0 $DHUB/myweb:v1.0.0

docker login
# id/pw 입력

# dockerhub에 push
docker push $DHUB/myweb:v1.0.0

 

 

- dockerhub 에 들어가서 로그인해보면 방금 푸쉬한 이미지가 내 레포에 올라가 있는 것을 볼 수 있다.

 

 

- 로컬에 이미지가 없어도 해당 저장소의 이미지를 끌어다 쓸 수 있다.

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

# 이미지가 없는 상태에서 재구동
docker run -d -p 80:80 --rm --name myweb $DHUB/myweb:v1.0.0

# 구동 확인
curl localhost

 

새로 실행 시 로컬에 이미지가 없으므로 새로 가져온다는 로그를 볼 수 있고, 정상 실행된다!

 

 

 

 

2. Jenkins

- Jenkins는 자동 빌드/자동 테스트/CI를 지원한다.

- 다양한 플러그인과 GUI 인터페이스를 제공하여 보다 쉽게 사용할 수 있다.

 

설치

sudo su -

# JAVA 설치
sudo yum install fontconfig java-17-amazon-corretto -y
JAVA_HOME=/usr/lib/jvm/java-17-amazon-corretto.x86_64
echo $JAVA_HOME

# 확인
java -version
alternatives --display java

# Jenkins 설치
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

# 초기 패스워드 확인
cat /var/lib/jenkins/secrets/initialAdminPassword

 

설치가 잘 되었음을 볼 수 있다.

출력한 주소로 들어가서 초기 비밀번호를 찍고, 추천 플러그인을 설치한다. (설치에 5분 정도 걸린다)

초기 플러그인 설치 이후에는 admin user를 생성한다. 시키는 대로 하면 된다.

 

 

 

- 그리고 Jdk version을 주입해 준다.

 

- add 후 save를 까먹지 말자!

/usr/lib/jvm/java-17-amazon-corretto.x86_64 doesn’t look like a JDK directory 라고 무섭게 빨간색으로 경고가 뜨는데 그냥 무시하면 된다.

 

 

- 새로운 item -> create a job 으로 들어가서 새 작업을 만들어 보자.

- create a job -> build steps (꽤 내려가야 있다) 에서 command 를 넣어 주는 것으로 새 빌드 스크립트를 만든다. 실습에서는 Execute shell을 해서 shell command를 실행하는 걸로 했지만 실무에서는 다양한 옵션을 선택할 수 있을 것 같다.

 

그리고 apply 후 저장 하는 것을 잊지 말 것! (알고 싶지 않았음)

 

- '지금 빌드' 를 클릭해서 빌드를 수행한다. 하단 히스토리에서 결과를 확인할 수 있다.

- 빌드 히스토리에서 각 빌드의 토글을 클릭하면 콘솔 아웃풋을 직접 확인할 수도 있다. 

 

젠킨스로 도커 사용

- 젠킨스를 깔면서 생성된 유저 'jenkins' 로 도커이미지를 빌드할 수도 있다.

grep -i jenkins /etc/passwd
usermod -s /bin/bash jenkins
grep -i jenkins /etc/passwd

# 권한 설정 변경
chmod 666 /var/run/docker.sock
usermod -aG docker jenkins

# Jeknins 유저로 전환
su - jenkins

# docker login
docker login

mkdir -p ~/myweb2 && cd ~/myweb2

# Dockerfile 파일 생성
vi Dockerfile
FROM ubuntu:20.04
ENV TZ=Asia/Seoul VERSION=2.0.0 NICK=<자신의 닉네임>
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"]

# build 
docker build -t myweb:v2.0.0 .

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

 

잘 되는 것을 확인한다.

GitHub-Jenkins 연동

- 우선 내 계정으로 가시다님의 레포를 떠온다.

- Jenkins에서 새 프로젝트를 만든다(이름은 Trigger-Project, 스타일은 프리스타일)

 

- 설정 내용은 아래와 같다.

Additional Behaviors -> Sparse Checkout Paths 지정은 여기에 명시된 디렉토리만 빌드하게 한다.

Poll SCM에는 밑에 * * * * * 을 명시해주면 매 분마다 변경사항을 확인한다! 는 뜻이다

깃헙 레포에 들어간 변경사항을 반영해야 하는 만큼, 빌드 스텝에서 내 레포의 코드를 가져오는 부분이 추가되었다.

 

 

 

- 이렇게 메인브랜치에 커밋이 발생할 때마다 빌드가 실행되었음을 확인할 수 있다

 

+빌드 실패: 현 디렉토리 구조상 Dockerfile이 폴더 안에 있어서 그런 것 같다. 빌드하는 경로를 specific 하게 지정하면 문제없이 빌드 될 것 같다.

 

 

 

Pipeline

- 파이프라인은 선언형과 스크립트형이 있는데, 선언형이 권장된다. 그러나 스크립트형은 보다 세밀한 커스텀이 용이하다는 장점이 있다.

 

- 새 파이프라인을 만들고, 아래 예시 파이프 코드를 이용한다.

pipeline {
    agent any

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

 

- 와 이래서 젠킨스 젠킨스 하는구나! 각 배포에 대해서 어떻게 진행되었는지 확인할 수 있다는 점이 큰 장점이다.

(왜 두개가 되었냐면... 더블클릭을 해서)

Secret text 실습

- Dashboard -> Jenkins 관리 -> Credentials -> System -> Global credentials 로 들어가서 secret text를 생성할 수 있다

- 마찬가지로 credentials 메뉴에서 새로 만든 credential을 확인한다

- 새 secret_pipeline을 만들고 아래 파이프 코드를 사용한다.

pipeline {
    agent any
    environment {
        SSHPW_CREDS = credentials('vmsshpw')
    }

    stages {
        stage('remote ssh') {
            steps {
                sh('echo ${SSHPW_CREDS}')
                sh('echo ${SSHPW_CREDS_PSW}')
                sh('sshpass -p $SSHPW_CREDS_PSW ssh root@10.10.1.1 hostname')
            }
        }
    }
}

 

- credential이 사용되었고 echo 하는 빌드가 성공했다!

그렇지만 echo에서도 credential의 원문이 나오지 않는 것은 꽤나 인상깊었다.

 

 

 

3. Jenkins & Kubernetes

- JenKins 에서 K8s를 사용하기 위한 사전 준비가 필요하다.
1. (Jenkins 계정에서) Jenkins 디렉토리 안에 Kube 디렉토리를 하나 만들어주고

2. root 계정에서 config를 복사해준다

3. Jenkins 계정에서 aws configure로 자격증명을 설정해주고 나면

-> kubectl 명령어를 사용할 수 있다!

# 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]: ***
AWS Secret Access Key [None]: ###
Default region name [None]: ap-northeast-2
Default output format [None]: json

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

 

 

- 그러면 Jenkins 에서 k8s를 배포하는 파이프라인을 만들어 보자.

파이프라인 코드는 역시 가시다님이 제공해주셨다.

pipeline {
    agent any

    tools {
        jdk 'jdk-17'
    }

    environment {
        DOCKERHUB_USERNAME = 'gasida' # 내 레포로 바꾸기!
        GITHUB_URL = 'https://github.com/gasida/aews-cicd.git' # 이것도 바꾸끼끼!
        // deployment-svc.yaml -> image: gasida/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"
            }
        }
    }
}

 

에서 '지금 빌드' 클릭해서 빌드!

 

- 처음에는 실패했다. 파이프라인 코드에 있는 도커허브 계정에 가시다님 게 그대로 있고 + 깃헙 레포 경로도 그대로였기 때문이다

 

- 수정해서 빌드에 성공했다!

 

- Kubectl get pods -A 로 찍어보면 새 pod이 잘 배포되었고 돌아가고 있음을 볼 수 있다

- 그리고 다음 실습을 위해 이번 실습으로 만들어진 리소스는 삭제해준다.

kubectl delete deploy,svc myweb

 

4. Argo

- ArgoCD는 Kubernetes native 한 Gitops CD 툴이다. Gitops라면 특정 브랜치에 특정 이벤트가 발생할 때마다 자동으로 k8s에 배포 프로세스를 진행해 주는 툴이다,

 

설치

- helm 으로 설치한다

# 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/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-14T08:12:16Z
applicationsets.argoproj.io                  2024-04-14T08:12:17Z
appprojects.argoproj.io                      2024-04-14T08:12:16Z

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

 

- 잘 설치되었고, 최초 접속 암호를 확인해서 접속한다.

 

-kube-ops-view 에서 각 노드에 pod이 2-3개씩 추가된 것을 볼 수 있다.

++DEBUG: 잘 설치되었고, 리소스도 확인되지만 접속이 안 되었는데, $CERT_ARN이 없었기 때문이었다.

$CERT_ARN은 터미널을 새로 열거나 하면 새로 저장해주어야 한다. 여담으로 여기서 꽤 시간을 많이 썼는데, 등록 후 gui가 뜨기까지 prometheus/grafana 보다 체감상 더 오랜 시간이 걸리는 것 같다... 그래서 기다리다가 잘못된 줄 알고 지우고 새로깔고, 스택 날리고 재배포 하고 했는데 원래 그런 듯!

아무튼 설치는 잘 되었다.

 

 

 

- 그러면 Applications 의 New app으로 들어가서 새로운 배포를 만들어 보자

Sync -> synchronize 를 눌렀는데 자꾸 sync 실패가 떠서 github 주소를 다시 복붙하고

- 악분 님의 블로그를 참고해서 수동 리프레시를 시행했다. 리프레시가 수행되고 pod이 잘 배포되었음을 터미널 모니터와 Argo 모두에서 확인할 수 있다.

 

 

Argo Rollout

- 기본적으로 Kubernetes는 롤링 업데이트를 수행하는데 여기에 좀더 최적화된 Argo 툴이라고 볼 수 있다. 아래와 같은 아키텍처로 구성되어 있고 blue-green update, canary update 등을 수행한다.

 

 

- 아래 코드를 이용해서 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

 

- 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

 

해서 직접 rolling update를 수행할 수도 있다.

 

 

 

 

잊지 말아요 자원 삭제

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

 

 

 

오늘은 평소에 관심있었던 Jenkins와 Argo에 대해서 실습할 수 있었다..! 최근에는 Github Action도 많이 사용하는 것 같은데 시간이 될 때 직접 실습해보아야겠다.