본문 바로가기

k8s

eks + argo cd +jenkins 로 ci/cd 구축하기

728x90

eks를 구성하고 ingress를 미리 다 사전에 셋팅 해논것을 전제로 시작하겠습니다.

ingress구성하는방법은 여기 블로그를 참고하시면 되겠습니다. eks 구성하는방법은 저작권이 있어서 못올려드립니다ㅠㅠ

https://velog.io/@lijahong/0%EB%B6%80%ED%84%B0-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-AWS-%EA%B3%B5%EB%B6%80-EKS-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0-Nginx-Ingress-%EC%84%A4%EC%B9%98-%EB%B0%8F-%ED%99%9C%EC%9A%A9#2-ingress-nginx-controller-admission

 

0부터 시작하는 AWS 공부 - EKS 활용하기 - Nginx Ingress 설치 및 활용

AWS EKS 활용하기 - Nginx Ingress 설치 및 활용

velog.io

argocd 설치하는방법

ArgoCD 설치 + 웹 접속

ArgoCD 설치 + 웹 접속

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

kubectl -n argocd get all

svc의 타입을 로드밸런서로 바꾸면

namespace - argocd

이렇게 dns 주소가 나와서 이를 통해 접속

 

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo

위 코드로 admin 초기 비밀번호를 얻을 수 있다

admin / passwd

로 접속

 

Repository 연결

argoCD 메인 - 설정 - repository - CONNECT REPO USING HTTPS 선택

private

일단 username 하고 password 를 입력하긴 했는데

repo 가 public 이면 굳이 입력 안해도 연결이 된다

successful

굿

 

ArgoCD Application 생성

general

sync policy 는 git repo 의 변경사항을 자동으로 탐지하기 위해 Automatic 으로 설정하고

self heal 까지 선택

 

source

repo url 은 connect 에서 연결해줬기때문에 목록에서 선택해주고

배포할 app의 git repo 에서의 위치를 path 에 입력해줌

나는 yaml 파일들을 prod 에 넣어놨음

aicore0934:~/environment $ kubectl create namespace demo-prod-1
namespace/demo-prod-1 created
aicore0934:~/environment $ kubectl get namespace
NAME              STATUS   AGE
argocd            Active   17m
default           Active   29h
demo-prod-1       Active   5s
kube-node-lease   Active   29h
kube-public       Active   29h
kube-system       Active   29h
aicore0934:~/environment $

EKS 에 namespace 하나 만들어주고

 

destination

목적지 설정해주고 create

 

SYNC 확인

app status

status 가 초록불

applications

argocd 를 쓰는 이유인것 같은데

이렇게 diagram 식으로 배포환경을 보여줘서 많이 쓰는것 같다

맨 우측 pod 까지 모두 정상 배포된 모습

 

 

 

deployment와 service를 조합한 yaml

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: teamplanner-api-deployment
  namespace: minwoo
spec:
  replicas: 2
  minReadySeconds: 5
  selector:
    matchLabels:
      app: teamplanner-api
  strategy:
    rollingUpdate:
      maxSurge: 25%
  template:
    metadata:
      labels:
        app: teamplanner-api
    spec:
      containers:
        - name: teamplanner-api-container
          image: 129715120090.dkr.ecr.ap-northeast-2.amazonaws.com/teamplanner-backendserver:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          envFrom:
            - secretRef:
                name: minwoo-secrets
---
apiVersion: v1
kind: Service
metadata:
  namespace: minwoo
  name: teamplanner-svc
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  selector:
    app: teamplanner-api

gitops 방식으로 jenkinsfile구성

pipeline {
    agent any
    options {
    timeout(time: 1, unit: 'HOURS') // set timeout 1 hour
    }

    environment {

        TIME_ZONE = 'Asia/Seoul'

        //github
        TARGET_BRANCH = 'develop'
        REPOSITORY_URL= 'https://github.com/SWM-304/TeamPlanner-BE.git'
        gitEmail = 'kbsserver@naver.com'
        gitName = 'minwoo1999'

        //docker-hub
        registryCredential = 'docker-hub'

        //aws ecr

        CONTAINER_NAME = 'teamplanner-backend-container'
        AWS_CREDENTIAL_NAME = 'AWS_ECR'
        ECR_PATH = '129715120090.dkr.ecr.ap-northeast-2.amazonaws.com'
        IMAGE_NAME = '129715120090.dkr.ecr.ap-northeast-2.amazonaws.com/teamplanner-backendserver'
        REGION = 'ap-northeast-2'
    }



    stages {


        stage('init') {
            steps {
                echo 'init stage'
                deleteDir()
            }
            post {
                success {
                    echo 'success init in pipeline'
                }
                failure {
                    error 'fail init in pipeline'
                }
            }
        }

        stage('Prepare') {
            steps {
                echo 'Cloning Repository'
                git branch: 'develop',
                    credentialsId: 'repo-and-hook-access-token-credentials',
                    url: 'https://github.com/SWM-304/TeamPlanner-BE.git'
            }
            post {
                success {
                    echo 'Successfully Cloned Repository'
                }
                failure {
                    error 'This pipeline stops here...'
                }
            }
        }
       // 일단은 테스트없이 빌드
        stage('Build Gradle') {
            steps {
                echo 'Build Gradle'

                dir('.'){
                    sh '''
                        pwd
                        cd /var/jenkins_home/workspace/teamPlannerBackEnd_jenkinsFile
                        chmod +x ./gradlew
                        ./gradlew build --exclude-task test
                    '''
                }
            }
            post {
                failure {
                    error 'This pipeline stops here...'
                }
            }
        }

        // 도커 이미지를 만든다. build number로 태그를 주되 latest 태그도 부여한다.
        stage('Build Docker') {
            steps {
                echo 'Build Docker'
                sh """
                    cd /var/jenkins_home/workspace/teamPlannerBackEnd_jenkinsFile
                    docker builder prune
                    docker build -t $IMAGE_NAME:$BUILD_NUMBER .
                    docker tag $IMAGE_NAME:$BUILD_NUMBER $IMAGE_NAME:latest
                """
            }
            post {
                failure {
                    error 'This pipeline stops here...'
                }
            }
        }


     // 빌드넘버 태그와 latest 태그 둘 다 올린다.
        stage('Push Docker') {
            steps {
                echo 'Push Docker'
                script {
                    // cleanup current user docker credentials
                    sh 'rm -f ~/.dockercfg ~/.docker/config.json || true'

                    docker.withRegistry("https://${ECR_PATH}", "ecr:${REGION}:${AWS_CREDENTIAL_NAME}") {
                        docker.image("${IMAGE_NAME}:${BUILD_NUMBER}").push()
                        docker.image("${IMAGE_NAME}:latest").push()
                    }
                }
            }
            post {
                failure {
                    error 'This pipeline stops here...'
                }
            }
        }

    stage('K8S Manifest Update') {
            steps {
                // Change directory to the workspace
                dir("/var/jenkins_home/workspace/teamPlannerBackEnd_jenkinsFile") {

                    git branch: 'main',
                    credentialsId: 'repo-and-hook-access-token-credentials',
                    url: 'https://github.com/SWM-304/Teamplanner-gitopsRepository'

                    // argocd manifest
                    sh "git config --global user.email '${gitEmail}'"
                    sh "git config --global user.name '${gitName}'"
                    // 이미지 태그를 변경
                    sh """
                        current_tag=\$(grep -oP 'image: 129715120090.dkr.ecr.ap-northeast-2.amazonaws.com/teamplanner-backendserver:\\K\\d+' scripts/service.yaml)
                       if [ "\$current_tag" -lt "\$BUILD_NUMBER" ]; then
                                rm -rf build
                                sed -i "s@image: 129715120090.dkr.ecr.ap-northeast-2.amazonaws.com/teamplanner-backendserver:\$current_tag@image: 129715120090.dkr.ecr.ap-northeast-2.amazonaws.com/teamplanner-backendserver:$BUILD_NUMBER@g" scripts/service.yaml
                                git add . # 변경된 파일을 스테이징 영역에 추가
                                git commit -a -m 'fix:${IMAGE_NAME} ${BUILD_NUMBER} image versioning' # 커밋을 만듭니다.
                                git branch -M main
                                git remote remove origin
                                git remote add origin https://github.com/SWM-304/Teamplanner-gitopsRepository
                                git push origin main
                       fi
                    """

                }
            }
            post {
                    failure {
                      echo 'K8S Manifest Update failure !'
                    }
                    success {
                      echo 'K8S Manifest Update success !'
                    }
            }
        }



    stage('Clean Up Docker Images on Jenkins Server') {
        steps {
            echo 'Cleaning up unused Docker images on Jenkins server'

            // Clean up unused Docker images, including those created within the last hour
            sh "docker image prune -f --all --filter \"until=1h\""
        }
    }




}

    post {
        success {
            slackSend (channel: '#cicd-notification', color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        failure {
            slackSend (channel: '#cicd-notification', color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}

내가 구성한 워크플로우

 

다음처럼 구성을 해놓았고 argocd가 정상적으로 돌아가게되었지만 이슈가 생겼다..

 

다음 그림처럼 기존의 모든 deployment가 남겨져 있는 이슈가 발생하였다.

기존 deployment가 자동으로 삭제되는게 정상인건지 아니면 남아있는게 정상인지 몰라 이리저리 알아봤다.

 

deployment 아래의 replicaset의 revision이 남아있어야 나중에 롤백 같은 걸 할 수 있어서 존재한다. 기본적으로는 전부 저장은 해놓지만 원한다면 revisionHistoryLimit을 지정하여 해당하는 revisition의 갯수 제한을 설정할 수 있다.

 

 

밑에 공식문서를 활용해서 나중에 revisitionLimit를 설정하여 갯수제한을 해봐야겠다.

 

https://kubernetes.io/ko/docs/concepts/workloads/controllers/deployment/#%EC%A0%95%EC%B1%85-%EC%B4%88%EA%B8%B0%ED%99%94

 

디플로이먼트

디플로이먼트(Deployment) 는 파드와 레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다. 디플로이먼트에서 의도하는 상태 를 설명하고, 디플로이먼트 컨트롤러(Controller)는 현재 상태에서 의

kubernetes.io

https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-back-a-deployment

 

Deployments

A Deployment manages a set of Pods to run an application workload, usually one that doesn't maintain state.

kubernetes.io

 

728x90