Helm과 Kustomize 비교하며 사용하기 - 2

appti·2024년 4월 18일
0

쿠버네티스 인강

목록 보기
14/15

서론

해당 글은 일프로 님의 인프런 강의 쿠버네티스 어나더 클래스 (지상편) - Sprint 1, 2의 내용을 정리한 글입니다.

해당 글에 사용된 내용, 사진 및 그림은 모두 강의와 강의 자료에 포함된 내용입니다.

Helm & Kustomize 패키지

  • 패키지 생성 방법
    • Helm : helm create api-tester
    • Kustomize : 직접 디렉토리 생성
      • Kustomize 구조를 알고 있어야 함
  • 파일 개수
    • Helm > Kustomize
  • 배포할 파일 선택 방법
  • 공통 값 설정

Kustomize 배포 실습

  • 파이프라인 생성

  • 내용 변경

  • 배포 동작 확인

  • Kustomize yaml 확인 가능

  • 파라미터와 함께 빌드 가능

  • 배포 정상 동작 확인

Jenkinsfile

pipeline {
    agent any

    tools {
        gradle 'gradle-7.6.1'
        jdk 'jdk-17'
    }

    // 빌드 시 같이 지정할 파라미터 
    parameters {
        choice(choices: ['dev', 'qa', 'prod'], name: 'PROFILE')
    }

    environment {
        // 본인의 username으로 하실 분은 수정해주세요.
        DOCKERHUB_USERNAME = '1pro'
        GITHUB_URL = 'https://github.com/k8s-1pro/kubernetes-anotherclass-sprint2.git'

        // 실습 넘버링
        CLASS_NUM = '2222'
    }

    stages {
        stage('소스파일 체크아웃') {
            steps {
                // 소스코드를 가져올 Github 주소
                git branch: 'main', url: 'https://github.com/k8s-1pro/kubernetes-anotherclass-api-tester.git'
            }
        }

        stage('소스 빌드') {
            steps {
                // 755권한 필요 (윈도우에서 Git으로 소스 업로드시 권한은 644)
                echo "chmod +x ./gradlew"
                echo "gradle clean build"
            }
        }

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

        stage('컨테이너 빌드') {
            steps {
                // jar 파일 복사
                echo "cp ./build/libs/app-0.0.1-SNAPSHOT.jar ./${CLASS_NUM}/build/docker/app-0.0.1-SNAPSHOT.jar"

                // 도커     빌드
                echo "docker build -t ${DOCKERHUB_USERNAME}/api-tester:v1.0.0 ./${CLASS_NUM}/build/docker"
            }
        }

        stage('컨테이너 업로드') {
            steps {
                // DockerHub로 이미지 업로드
                echo "docker push ${DOCKERHUB_USERNAME}/api-tester:v1.0.0"
            }
        }

        stage('커스터마이즈 템플릿 확인') {
            steps {
                // K8S 배포
                sh "kubectl kustomize ./${CLASS_NUM}/deploy/kustomize/api-tester/overlays/${params.PROFILE}"
            }
        }

        stage('커스터마이즈 배포') {
            steps {
                // K8S 배포
                input message: '배포 시작', ok: "Yes"
                // 빌드 시 지정한 PROFILE
                // kubectl을 통해 배포 진행 
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/kubectl/namespace-${params.PROFILE}.yaml"
                // -k 옵션을 통해 Kustomize 배포
                sh "kubectl apply -k ./${CLASS_NUM}/deploy/kustomize/api-tester/overlays/${params.PROFILE}"
            }
        }
    }
}

다양한 배포 환경을 위한 Helm 배포

  • Kustomize 배포 실습와 동일한 방식

  • 파이프라인 추가

  • 내용 변경

  • 배포 동작 확인

Jenkinsfile

pipeline {
    agent any

    tools {
        gradle 'gradle-7.6.1'
        jdk 'jdk-17'
    }

    parameters {
        choice(choices: ['dev', 'qa', 'prod'], name: 'PROFILE')
    }

    environment {
        // 본인의 username으로 하실 분은 수정해주세요.
        DOCKERHUB_USERNAME = '1pro'
        GITHUB_URL = 'https://github.com/k8s-1pro/kubernetes-anotherclass-sprint2.git'

        // 실습 넘버링
        CLASS_NUM = '2223'
    }

    stages {
        stage('소스파일 체크아웃') {
            steps {
                // 소스코드를 가져올 Github 주소
                git branch: 'main', url: 'https://github.com/k8s-1pro/kubernetes-anotherclass-api-tester.git'
            }
        }

        stage('소스 빌드') {
            steps {
                // 755권한 필요 (윈도우에서 Git으로 소스 업로드시 권한은 644)
                echo "chmod +x ./gradlew"
                echo "gradle clean build"
            }
        }

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

        stage('컨테이너 빌드') {
            steps {
                // jar 파일 복사
                echo "cp ./build/libs/app-0.0.1-SNAPSHOT.jar ./${CLASS_NUM}/build/docker/app-0.0.1-SNAPSHOT.jar"

                // 도커 빌드
                echo "docker build -t ${DOCKERHUB_USERNAME}/api-tester:v1.0.0 ./${CLASS_NUM}/build/docker"
            }
        }

        stage('컨테이너 업로드') {
            steps {
                // DockerHub로 이미지 업로드
                echo "docker push ${DOCKERHUB_USERNAME}/api-tester:v1.0.0"
            }
        }

        stage('헬름 템플릿 확인') {
            steps {
                // K8S 배포
                // -f 옵션을 통해 values-{profile}.yaml을 만들어서 배포 환경마다 별도의 값들을 세팅하는 방식 
                sh "helm template api-tester-${CLASS_NUM} ./${CLASS_NUM}/deploy/helm/api-tester" +
                   " -f ./${CLASS_NUM}/deploy/helm/api-tester/values-${params.PROFILE}.yaml -n anotherclass-222-${params.PROFILE}"
                // 배포 시마다 값을 동적으로 지정해야 하는 경우에 set 사용
                // --set replicaCount='3' --set port='80' --set profile='dev' --set nodeport='32223'
            }
        }

        stage('헬름 배포') {
            steps {
                input message: '배포 시작', ok: "Yes"
                // 네임스페이스를 분리해 배포
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/kubectl/namespace-${params.PROFILE}.yaml"
                // 최초 실행이면 install 아니면 upgrade
                sh "helm upgrade api-tester-${CLASS_NUM} ./${CLASS_NUM}/deploy/helm/api-tester" +
                   " -f ./${CLASS_NUM}/deploy/helm/api-tester/values-${params.PROFILE}.yaml" +
                   " -n anotherclass-222-${params.PROFILE} --install"  //  --create-namespace
            }
        }
    }
}

배포 파이프라인 구축 후 마주하게 되는 고민들

  • 중요 데이터 암호화 관리
    • 도커 로그인 관련 데이터 config.json는 암호화가 되지 않음
    • kubeconfig를 복사하면 다른 곳에서 관리자 권한으로 API 호출 가능
    • 젠킨스 Credential을 통해 암호화
    • 도커의 경우 로그인할 때 마다 config.json이 생성되기 때문에 로그아웃해 해당 파일의 내용 삭제
  • 컨테이너 업로드 후 Server에 이미지가 쌓이는 경우
    • 젠킨스가 디스크 관련 문제로 죽기 전에 삭제할 것
  • 네임스페이스는 배포와 인프라를 분리해서 관리
  • Helm 부가 기능 중 --wait를 통해 배포 종료 체크
  • Helm 부가 기능 중 metadata.annotations를 통해 새 배포시마다 랜덤값을 생성해 항상 쿠버네티스에서 변경 사항을 감지하도록 설정
  • 이미지 태그
    • 배포 환경 별 태그 전략
      • 개발 환경에서는 잦은 배포로 인해 버저닝이 무의미
        • latest를 추가하고 pullPolicy Always를 통해 항상 DockerHub에서 최신 이미지를 조회하도록 설정
      • 검증/운영 환경에서는 계획된 배포이므로 버저닝 필수
        • 태그를 명시적으로 사용하고 pullPolicy IfNotPresent를 통해 스케일 인/아웃 시 효율적으로 처리
    • 롤백을 위한 배포 전략
      • 개발 환경에서는 날짜 & 시간과 같은 다양한 시퀀스 추가
        • pullPolicy로는 IfNotPresent 사용
        • 항상 값이 변경되기 때문에 metadata.annotations는 사용하지 않음
  • 이미지 태그 latest
    • 일반적으로 최신 안정화 버전으로 가져가는 것이 일반적
  • 인프라 환경 이미지
    • 쿠버네티스 GC가 사용하지 않는 이미 자동 삭제

실습

  • 파이프라인 추가

  • 내용 변경

// Docker 사용
steps {
  script{
    withCredentials([usernamePassword(credentialsId: 'docker_password', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')]) {
    sh "echo " + '${PASSWORD}' + " | docker login -u " + '${USERNAME}' + " --password-stdin"

// Kubernetes config 사용
steps {
  withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {    // 암호화로 관리된 config가 들어감
    sh "kubectl apply -f ./2224/deploy/kubectl/namespace-dev.yaml --kubeconfig " + '${KUBECONFIG}'
    sh "helm upgrade api-tester-2224 ./2224/deploy/helm/api-tester -f ./2224/deploy/helm/api-tester/values-dev.yaml" +
        " -n anotherclass-222-dev --install --kubeconfig " + '${KUBECONFIG}'
  • Credentials에 지정한 docker_passwor와 k8s_master_config를 사용하고 있음을 확인할 수 있음
# 도커 로그아웃
docker logout

# 도커 로그인 정보 삭제 하ㅗㄱ인
cat ~/.docker/config.json

# kubeconfig 파일명 변경
mv ~/.kube/config ~/.kube/config_bak

# 인증서인 kubeconfig 파일을 찾을 수 없으므로 실패 
kubectl get pods -A
  • CI/CD 서버에서는 어떠한 정보도 남기지 않을 것을 권장
environment {
  APP_VERSION = '1.0.1'
  BUILD_DATE = sh(script: "echo `date +%y%m%d.%d%H%M`", returnStdout: true).trim()
  TAG = "${APP_VERSION}-" + "${BUILD_DATA}"

stage('컨테이너 빌드 및 업로드') {
  steps {
	script{
	  // 도커 빌드
      sh "docker build ./2224/build/docker -t 1pro/api-tester:${TAG}"
      sh "docker push 1pro/api-tester:${TAG}"

stage('헬름 배포') {
  steps {
    withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
      sh "helm upgrade api-tester-2224 ./2224/deploy/helm/api-tester -f ./2224/deploy/helm/api-tester/values-dev.yaml" +
         ...
         " --set image.tag=${TAG}"   // Deployment의 Image에 태그 값 주입
  • 시간 기준으로 태그 값 설정
  • Helm 배포 시 set을 통해 동적으로 태그 값 설정
docker tag 1pro/api-tester:1.0.1-231220.171834 1pro/api-tester:latest
docker push 1pro/api-tester:latest
  • 최신 안정화 버전을 선정한 뒤 수동으로 도커 이미지 태그 변경
stage('컨테이너 빌드 및 업로드') {
  steps {
	script{
	  // 도커 빌드
      sh "docker build ./${CLASS_NUM}/build/docker -t ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
      sh "docker push ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
      sh "docker rmi ${DOCKERHUB_USERNAME}/api-tester:${TAG}"   // 이미지 삭제
      
      ...
    }
  }
}
  • 업로드 이후 CI/CD 서버에 만들어진 이미지 삭제
    • docker rmi를 통해 이미지 삭제
stage('네임스페이스 생성') {  // 배포시 apply로 Namespace 생성 or 배포와 별개로 미리 생성 (추후 삭제시 별도 삭제)
  steps {
    withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
      sh "kubectl apply -f ./2224/deploy/kubectl/namespace-dev.yaml --kubeconfig " + '"${KUBECONFIG}"'
...
stage('헬름 배포') {
  steps {
  • 네임스페이스와 배포 분리
    • 현업에서는 네임스페이스를 한 번 만든 뒤 수정할 일이 거의 없음
    • 초반에 직접 kubectl로 네임스페이스를 만들고 배포 스크립트에서는 제외하거나 네임스페이스 관련 파이프라인을 별도로 만들 수도 있음
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:      
      annotations:  
        rollme: {{ randAlphaNum 5 | quote }} // 항상 새 배포를 위해 랜덤값 적용
  • 배포 후 강제적으로 업그레이드를 수행할 수 있도록 랜덤 값 부여
stage('헬름 배포') {
  steps {
    withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
      sh "helm upgrade api-tester-2224 ./2224/deploy/helm/api-tester -f ./2224/deploy/helm/api-tester/values-dev.yaml" +
         ...
         " --wait --timeout=10m" +  // 최대 10분으로 설정
  • Helm 부가 기능을 통해 배포 종료 체크
# GC 속성 관련 설정 파일
vi /var/lib/kubelet/config.yaml

# 변경한 설정 적용을 위한 재시작
systemctl restart kubelet

  • 빌드 정상 동작 확인

Jenkinsfile

pipeline {
    agent any

    tools {
        gradle 'gradle-7.6.1'
        jdk 'jdk-17'
    }

    parameters {
        // 배포 환경 선택
        choice(choices: ['dev', 'qa', 'prod'], name: 'PROFILE', description: '배포 환경 선택')
    }

    environment {
        // 본인의 username으로 하실 분은 수정해주세요.
        DOCKERHUB_USERNAME = '1pro'
        GITHUB_URL = 'https://github.com/k8s-1pro/kubernetes-anotherclass-sprint2.git'

        // [2] 잦은 배포 - versioning 무의미, 계획된 배포 - versioning 필수
        APP_VERSION = '1.0.1'
        BUILD_DATE = sh(script: "echo `date +%y%m%d.%d%H%M`", returnStdout: true).trim()
        TAG = "${APP_VERSION}-" + "${BUILD_DATE}"

        // 실습 넘버링
        CLASS_NUM = '2224'
    }

    stages {
        stage('소스파일 체크아웃') {
            steps {
                // 소스코드를 가져올 Github 주소
                git branch: 'main', url: 'https://github.com/k8s-1pro/kubernetes-anotherclass-api-tester.git'
            }
        }

        stage('소스 빌드') {
            steps {
                // 실제 소스 빌드 수행 
                // 755권한 필요 (윈도우에서 Git으로 소스 업로드시 권한은 644)
                sh "chmod +x ./gradlew"
                sh "gradle clean build"
            }
        }

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

        stage('컨테이너 빌드 및 업로드') {
            steps {
                // jar 파일 복사
                sh "cp ./build/libs/app-0.0.1-SNAPSHOT.jar ./${CLASS_NUM}/build/docker/app-0.0.1-SNAPSHOT.jar"

                script{

                    // 도커 빌드 - [1] 중요 데이터 암호화 관리
                    withCredentials([usernamePassword(credentialsId: 'docker_password', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')]) {
                        sh "echo " + '${PASSWORD}' + " | docker login -u " + '${USERNAME}' + " --password-stdin"
                    }

                    if (DOCKERHUB_USERNAME == "1pro") {
                        TAG = "1.0.1-231220.175735"  // 1pro useranme 수강생을 위한 고정 태그명

                        // 도커 빌드
                        sh "docker build ./${CLASS_NUM}/build/docker -t ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
                    } else {

                        // 도커 빌드
                        sh "docker build ./${CLASS_NUM}/build/docker -t ${DOCKERHUB_USERNAME}/api-tester:${TAG}"

                        // [2] 잦은 배포 - versioning 무의미, 계획된 배포 - versioning 필수
                        sh "docker push ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
                    }

                    // [3] 업로드 후 CI/CD Server에 만들어진 이미지 삭제
                    sh "docker rmi ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
                }
            }

            post {
                always {
                    sh "docker logout"
                }
            }
        }

        // [4] 네임스페이스는 배포와 별도로 관리
        stage('네임스페이스 생성') { // 배포시 apply로 Namespace 생성 or 배포와 별개로 미리 생성 (추후 삭제시 별도 삭제)
            steps {
                // kubectl 명령 사용 - [1] 중요 데이터 암호화 관리
                withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
                    sh "kubectl apply -f ./${CLASS_NUM}/deploy/kubectl/namespace-${params.PROFILE}.yaml --kubeconfig " + '${KUBECONFIG}'
                }
            }
        }

        stage('헬름 배포') {
            steps {
                // helm 명령 사용 - [1] 중요 데이터 암호화 관리
                // Jenkins 빌드 전 profile 동적 지정 
                withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
                    sh "helm upgrade api-tester-${CLASS_NUM} ./${CLASS_NUM}/deploy/helm/api-tester -f ./${CLASS_NUM}/deploy/helm/api-tester/values-${params.PROFILE}.yaml" +
                            " -n anotherclass-222-${params.PROFILE} --install --kubeconfig " + '${KUBECONFIG}' +

                            // [5] Helm 부가기능
                            " --wait --timeout=10m" +   // 최대 10분으로 설정

                            // [2] 잦은 배포 - versioning 무의미, 계획된 배포 - versioning 필수
                            " --set image.tag=${TAG}" +
                            " --set image.repository=${DOCKERHUB_USERNAME}/api-tester"
                }
            }
        }
    }
}
profile
안녕하세요

0개의 댓글