젠킨스 설치 및 설정하기, 젠킨스로 CI/CD 구현하기, 젠킨스 플러그인을 통해 구현되는 GitOps

노하람·2021년 9월 15일
3

쿠버네티스

목록 보기
1/1

젠킨스 설치 및 설정하기

젠킨스는 헬름을 사용하면 좀 더 쉽게 설치할 수 있습니다. 하지만 헬름으로 설치한다고 해도 젠킨스를 설치하려면 사전에 준비가 필요합니다. 하나씩 같이 진행해봅시다!

헬름으로 젠킨스 설치하기

헬름 실습 떄 사용했던 차트 저장소 edu는 앞으로 사용할 모든 애플리케이션이 차트로 등록돼 있습니다. 따라서 지금부터 진행하는 실습에서는 차트 저장소를 새로 등록하지 않고 바로 애플리케이션을 설치하겠습니다.

  1. 젠킨스로 지속적 통합을 진행하는 과정에서 컨테이너 이미지를 레지스트리에 푸시하는 단계가 있습니다.
    • 이때 미지를 저장하는 레지스트리는 앞선 포스팅에서 구성한 이미지 레지스트리를 사용합니다.
    • 따라서 다음과 같은 실행 결과처럼 레지스트리 컨테이너가 나오지 않는다면 이전 레지스트리 구성 포스팅을 참고하여 레지스트리를 우선 구성하길 바랍니다.
    • docker ps -f name=registry
  1. 헬름으로 설치되는 젠킨스는 파드에서 동작하는 애플리케이션이기 때문에 PV를 마운트하지 않으면 파드가 다시 시작될 때 내부 볼륨에 저장하는 모든 데이터가 삭제됩니다.
    - 이를 방지하기 위해서 애플리케이션의 PV가 NFS를 통해 프로비저닝될 수 있게 NFS 디렉터리를 /nfs_shared/jenkins에 만들겠습니다.

    • 미리 정의된 nfs-exporter.sh jenkins를 실행합니다.

    • 이 스크립트에는 NFS용 디렉터리를 만들고 이를 NFS 서비스로 생성하는 과정이 담겨 있습니다. 상세 과정은 이전 포스팅의 PV와 PVC를 참조하시길 바랍니다.

    • ~/_Book_k8sInfra/ch5/5.3.1/nfs-exporter.sh jenkins

      nfsdir=/nfs_shared/$1
      if [ $# -eq 0 ]; then
        echo "usage: nfs-exporter.sh <name>"; exit 0
      fi
      
      if [[ ! -d $nfsdir ]]; then
        mkdir -p $nfsdir
        echo "$nfsdir 192.168.1.0/24(rw,sync,no_root_squash)" >> /etc/exports
        if [[ $(systemctl is-enabled nfs) -eq "disabled" ]]; then
          systemctl enable nfs
        fi
         systemctl restart nfs
      fi

  2. 만들어진 디렉터리에 부여된 사용자 ID(uid)와 그룹 ID(gid)의 번호를 -n 옵션으로 확인합니다.

    • 0번은 root 사용자에 속해 있다는 의미입니다.
    • ls -n /nfs_shared
    • 사용자와 그룹 모두 0이라는 것이 확인되었습니다.
  3. 젠킨스를 헬름 차트로 설치해 애플리케이션을 사용하게 되면 젠킨스의 여러 설정 파일과 구성 파일들이 PVC를 통해 PV에 파일로 저장됩니다.

    • 이때 PV에 적절한 접근 ID를 부여하지 않으면 PVC를 사용해 파일을 읽고 쓰는 기능에 문제가 발생할 수 있습니다.
    • 이런 문제를 방지하기 위해서 chown 1000:1000 /nfs_shared/jenkins 명령어로 젠킨스 PV가 사용할 NFS 디렉터리에 대한 접근 ID(사용자ID, 그룹ID)를 1000번으로 설정하겠습니다.
    • 1000번으로 설정한 이유는 젠킨스 컨트롤러 이미지에서 기본적으로 사용하는 유저 ID와 그룹 ID가 1000번이기 때문입니다.
    • 젠킨스 컨트롤러를 포함한 젠킨스 구조에 대해서는 조금 후에 살펴보겠습니다.
    • chown 1000:1000 /nfs_shared/jenkins/
    • ls -n /nfs_shared

호스트 디렉터리와 젠킨스 컨트롤러의 ID 관계
젠킨스 컨트롤러 설치 후에 내부의 유저 ID와 그룹 ID를 살펴보면 1000번으로 설정돼 있는 것을 확인할 수 있습니다.
kubectl exec -it jenkins-~~ -- cat /etc/passwd
따라서 PV로 사용되는 NFS 디렉터리의 접근 ID를 동일하게 설정하지 않는다면 젠킨스 컨트롤러에서 이루어지는 작업들이 정상적으로 수행되지 않고 'fatal: unable to look up current user in the passwd file: no such user'와 같은 에러가 발생합니다.
젠킨스 설치 시 환경변수를 설정해서 해결하는 방법이 있으나 이는 수정해야 할 것들이 많으므로 현재의 랩 환경에서 가장 간단한 방법인 호스트 시스템의 ID와 젠킨스 컨트롤러 ID를 일치시켜주는 방법으로 해결합니다.

  1. 젠킨스는 사용자가 배포를 위해 생성한 내용과 사용자의 계정 정보, 사용하는 플러그인과 같은 데이터를 저장하기 위해서 PV와 PVCD의 구성을 필요로 합니다.

    • 사전 구성된 jenkins-volume.yaml을 이용해 PV와 PVC를 구성하고, 구성된 PV와 PVC가 Bound 상태인지 확인합니다.
    • kubectl apply -f ~/_Book_k8sInfra/ch5/5.3.1/jenkins-volume.yaml
    • kubectl get pv jenkins
    • kubectl get pvc jenkins
  2. 이제 모든 준비를 마쳤으니 젠킨스를 설치해 보겠습니다.

    • 젠킨스를 설치하는 데 필요한 인자가 많기 때문에 모든 인자를 포함해 사전에 구성한 jenkins-install.sh를 실행해 젠킨스를 설치하겠습니다.
    • 실행하고 나면 젠킨스 릴리스에 대한 정보가 나타나는 것을 확인할 수 있습니다.
    • ~/_Book_k8sInfra/ch5/5.3.1/jenkins-install.sh

      - NAME : 설치된 젠킨스의 릴리스 이름은 jenkins입니다. 이후 헬름 관련 명령으로 젠킨스를 조회, 삭제, 변경 등을 수행할 때 이 이름을 사용합니다!
      - NAMESPACE : 젠킨스가 배포된 네임스페이스는 default입니다.
      - REVISION : 배포된 릴리스가 몇 번쨰로 배포된 것인지 알려줍니다. 이 젠킨스는 처음 설치된 것임을 알 수 있습니다. helm upgrade 명령어를 사용해 젠킨스의 버전을 업그레이드할 때마다 REVISION은 1씩 증가합니다. 또한, 업그레이드 작업 후 이전 버전으로 돌아가기 위해 helm rollback 명령어를 사용할 수 있습니다. helm rollback 명령어 사용시 REVISION 번호를 직접 지정해 특정 리비전으로 돌아가도록 설정할 수도 있습니다.
      - NOTES : 설치와 관련된 안내 사항을 몇 가지 표시할 수 있습니다. NOTES의 1번 항목은 젠킨스의 관리자 비밀번호를 얻어오기 위한 명령어입니다. printf $(kubectl get secret --namespace default jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo, 2번은 젠킨스가 구동하는 파드에 접속할 수 있도록 외부의 트래픽을 쿠버네티스의 파드로 전달하게 만드는 설정입니다. 외부에서 쉽게 접속하기 위해서 이 실습에서는 트래픽을 전달하는 설정을 하지 않고 로드밸런서를 사용하겠습니다. 3번에 표시된 admin은 젠킨스 접속 시 사용할 유저 이름입니다.
  3. 순서대로라면 젠킨스의 코드를 살펴봐야 하지만 코드를 이해하기 위해서는 배포된 젠킨스 디플로이먼트의 정보를 함께 비교해서 봐야 합니다.

    • 따라서 우선 디플로이먼트가 정상적으로 배포됐는지 확인합니다.
    • kubectl get deployment

헬름으로 설치하는 애플리케이션 초기화 하는 법(설치/사용 중 문제 해결)
헬름을 통해 젠킨스가 잘 설치되는 경우도 있지만 시스템 및 환경에 따라서 Ready 값이 '0/1'에서 멈춰 있는 경우가 있습니다.
이런 경우에는 kubectl get pods 명령으로 상태를 추가 확인하고, 확인 결과가 init:0/1 상태(초기화 컨테이너 구동 대기)라면, 이는 주로 젠킨스에 필요한 파일(플로그인)을 내려받는 것이 느리거나 문제가 생긴 경우입니다.
따라서 최대 20분 정도를 대기한 후 해결되지 않는다면 helm uninstall jenkinsrm -rf /nfs_shared/jenkins/* 명령으로 설치 과정에서 내려 받은 파일들을 삭제하고 초기화 시킨 이후에 다시 젠킨스를 설치하길 바랍니다.
이후에 실습한 프로메테우스와 그라파나도 설치 또는 사용 중에 문제가 발생한 경우 동일하게 초기화해 문제를 해결할 수 있습니다.

  1. 배포된 젠킨스가 외부에서 접속할 수 있는 상태인지 서비스의 상태를 확인합시다.

    • 192.168.1.11 주소로 젠킨스가 외부에 노출된 것을 확인할 수 있습니다.
    • kubectl get service jenkins
  2. 젠킨스를 처음으로 배포해 봤으니 파드 상태도 한번 자세히 봅시다.

    • 그런데 젠킨스가 마스터 노드에 있습니다. 이게 어찌된 영문일까요? 마스터에도 파드가 배포될 수 있었는지 의문이 듭니다.
    • kubectl get pods -o wide
  3. 의문점을 해결하기 위해 kubectl get node m-k8s -o yaml | nlkubectl get deployment jenkins -o yaml | nl 명령을 통해서 상태를 비교해 보겠습니다.

    • 여기서 nlnumber lines of files의 약자로 줄 번호를 추가하는 역할입니다.

         164    taints:
         165    - effect: NoSchedule
         166      key: node-role.kubernetes.io/master
         561        tolerations:
         562        - effect: NoSchedule
         563          key: node-role.kubernetes.io/master
    • 출력되는 많은 내용 중에서 위에 표시된 테인트(taints)와 톨러레이션(tolerations)이 이런 결과를 만든 설정입니다.

테인트와 톨러레이션에 관하여

일반적으로 테인트와 톨러레이션은 혼합해서 사용하는데, 개념적으로 매우 혼동이 올 수 있으므로 비유를 들어 설명해 보겠습니다.

테인트는 손에 잡기 싫은 것, 피하고 싶은 것, 가지 말았으면 하는 것을 의미합니다.
테인트가 설정되면 그곳은 설거지가 끝난 이후에 수채구멍과 같습니다.
그렇지만 상황에 따라서 테인트가 설정돼 있는 곳을 꼭 만져야 할 때가 있습니다.
주로 아침에 나갈 때 버리는 음식물 쓰레기 같은 경우 말입니다.
이런 경우 인내하고 음식물 쓰레기를 처리해야 합니다.
그러기 위해선 톨러레이션, 즉 참아내는 인내가 필요한 것입니다.
쿠버네티스의 테인트와 톨러레이션은 사전적인 의미와 반대입니다.

위에서 언급한 것처럼 매우 특별하게 취급돼야 하는 곳에는 테인트를 설정해, 쉽게 접근하지 못하는 소중한 것으로 설정합니다.
그리고 톨러레이션이라는 특별한 키를 가져야만 이곳에 출입할 수 있습니다.
즉, 현재 상태에서는 마스터 노드에 테인트가 설정돼 있어 특별한 목적으로 사용되는 노드라는 것을 명시해 두었습니다.
일반적으로 마스터 노드 이외에도 GPU 노드, DB 전용 노드 등의 특별한 목적으로 사용될 때 주로 사용합니다.

이 포스팅에서는 관리의 편의를 위해 젠킨스 컨트롤러가 여러 곳에 스케줄되지 않고 마스터 노드인 m-k8s에서만 스케줄될 수 있도록 구성했습니다.
앞으로 컨트롤러가 가지는 애플리케이션 배포시에는 동일하게 마스터 노드에 컨트롤러를 구성하겠습니다.
테인트와 톨러레이션은 관계를 정의하는 것에 따라서 배포를 상당히 유연하게 만들 수 있습니다.

테인트는 키(key)와 값(value) 그리고 키와 값에 따른 효과(effect)의 조합을 통해 테인트를 설정한 노드에 파드 배치의 기준을 설정합니다.
그리고 톨러레이션은 테인트와 마찬가지로 키, 값, 효과를 가지고 있으며 이외에 연산자(operator)를 추가로 가지고 있습니다.
우선 테인트에 대한 요소를 살펴보면 키와 값의 조합은 테인트를 설정한 노드가 어떤 노드인지를 구분하기 위해 사용합니다.
키는 필수로 설정해야 하지만 값은 생략할 수도 있습니다. 9번 실습의 노드 명서에서 나타난 key: node-role.kubernetes.io/master는 이 노드가 마스터의 역할을 한다는 것을 나타내기 위해 작성된 것입니다.

효과는 테인트와 톨러레이션의 요소인 키 또는 값이 일치하지 않는 파드가 노드에 스케줄되려고 하는 경우 어떤 동작을 할 것인지를 나타냅니다.
효과는 NoSchedule, PreferNoSchedule, NoExecute을 값으로 가질 수 있는데 효과에 따라서 테인트를 설정한 노드는 파드를 새로 배치하는 경우와 파드가 이미 배치된 노드에 대한 동작이 다릅니다. 다음과 같이 정리할 수 있습니다.

효과테인트가 설정된 노드에 파드 신규 배치파드가 배치된 노드에 테인트 설정
NoSchedule노드에 파드 배치를 거부노드에 존재하는 파드 유지
PreferNoSchedule다른 노드에 파드 배치가 불가능할 때는 노드에 파드 배치노드에 존재하는 파드 유지
NoExecute노드에 파드 배치를 거부파드를 노드에서 제거

이번에는 톨러레이션의 요소들에 대해서 살펴보겠습니다.
톨러레이션은 테인트와 마찬가지로 키, 값, 효과를 가지고 있으며 연산자라는 특별한 요소를 가집니다.
톨러레이션은 테인트가 설정된 노드로 들어가기 위한 특별한 열쇠 역할을 하며 키와 효과는 바늗시 일치해야 합니다.
이때 톨러레이션에만 존재하는 연산자는 기본적으로 Equal로 동작해 테인트와 톨러레이션을 비교하는 역할을 합니다.
하지만 Exists의 경우에는 비교할 키와 값이 존재한다는 가정으로 테인트에 진입할 수 있는 만능 키로 바꿔주는 역할을 합니다.
하지만 그렇게 간단하지는 않으니 조금 더 자세히 살펴보겠습니다.

톨러레이션은 톨러레이션의 키, 값, 효과를 사용해 연산자를 통해 비교한 후 조건에 맞는 테인트를 식별합니다.
키와 효과 중 생략된 요소가 있다면 해당 요소는 묵시적으로 모든 키 혹은 모든 효과를 의미합니다.
톨러레이션의 키, 값, 효과는 테인트의 키, 값, 효과와 조건이 맞는지를 일치(Equal) 혹은 존재(Exists) 연산자를 통해서 판단합니다.
이떄 연산자를 생략할 경우에는 묵시적으로 Equal을 의미합니다.
조건 판단 결과 테인트와 톨러레이션의 조건이 맞다면 테인트가 설정된 노드에 톨러레이션을 가진 파드를 배치할 수 있습니다.
조건이 맞다고 판단하는 기준은 Equal 연산자를 사용했을 때 테인트와 톨러레이션의 키와 값 그리고 효과까지 일치하는 경우입니다.
Exists 연산자를 사용했을 때는 값은 반드시 생략해야 하며 이 상태에서 키와 효과의 일치 여부를 판단하게 됩니다.
또한 키와 효과를 모두 생략한 상태에서 Exists 연산자만 사용한다면 테인트의 키와 효과는 모든 키와 모든 효과를 의미하므로 Exists 연산자 하나만으로도 테인트가 설정된 모든 노드에 대해서 해당 톨러레이션을 설정한 파드를 배포할 수 있게 됩니다.

그러면 이제 헬름을 통해서 젠킨스를 설치했던 스크립트인 jenkins-install.sh의 내용을 살펴보겠습니다.
다수의 --set 값들을 통해서 현재 환경에 맞는 젠킨스를 설치할 수 있습니다.

#!/usr/bin/env bash
# 기본 설정으론 30분 넘게 사용하지 않으면 세션이 종료됩니다. 추가 설정을 통해 세션의 유효 시간을 하(1440분)으로 변경하고, 세션을 정리하는 시간 또한 하루(86400초)로 변경합니다.
jkopt1="--sessionTimeout=1440"
jkopt2="--sessionEviction=86400"
# 기본 설정으론 시간대가 정확히 맞지 않아 젠킨스를 통한 CI/CD 시엔 명확히 작업을 구분하기 힘듭니다. 그래서 서울(Asia/Seoul) 시간대로 변경합니다.
jvopt1="-Duser.timezone=Asia/Seoul"
# 쿠버네티스를 위한 젠킨스 에이전트 노드 설정은 Pod Template이라는 곳을 통해서 설정값을 입력합니다. 그런데 가상 머신인 마스터 노드가 다시 시작하면 이에 대한 설정이 초기화됩니다. 따라서 설정값을 미리 입력해둔 야믈 파일을 깃허브 저장소에서 받아오도록 설정합니다.
jvopt2="-Dcasc.jenkins.config=https://raw.githubusercontent.com/sysnet4admin/_Book_k8sInfra/main/ch5/5.3.1/jenkins-config.yaml"

# edu 차트 저장소의 jenkins 차트를 사용해 jenkins 릴리스를 설치합니다.
helm install jenkins edu/jenkins \
# PVC 동적 프로비저닝을 사용할 수 없는 가상 머신 기반의 환경이기 때문에 이미 만들어 놓은 jenkins라는 이름의 PVC를 사용하도록 설정합니다.
--set persistence.existingClaim=jenkins \
# 젠킨스 접속 시 사용할 관리자 비밀번호를 설정합니다. 이 값을 설정하지 않을 경우에는 설치 과정에서 젠킨스가 임의로 생성한 비밀번호를 사용해야합니다.
--set master.adminPassword=admin \
# 젠킨스의 컨트롤러 파드를 쿠버네티스 마스터 노드에 배치하도록 선택합니다. 11번 과정에서 마스터 노드는 kubernetes.io/hostname=m-k8s라는 레이블을 가지고 있는 것을 확인했습니다. nodeSelector는 nodeSelector의 뒤에 따라오는 문자열과 일치하는 레이블을 가진 노드에 파드를 스케줄링하겠다는 설정입니다. 그런데 .io 옆에 \가 붙어있는데 kubernetes.io에 '.'이 포함되어 있기 때문에 \가 없다면 하나의 문자열로 인식하지 못하고 kubernetes라는 속성과 이것의 하위 속성 io로 분리해서 인식하게 됩니다. 이를 방지하게 위해 이스케이프 문자를 붙여줍니다.
--set master.nodeSelector."kubernetes\.io/hostname"=m-k8s \
# 10번째 줄을 통해 마스터 노드에 파드를 배치할 것을 명시했지만 아래 세 줄이 없다면 마스터 노드에 파드를 배치할 수 없습니다. 현재 마스터 노드에는 파드를 배치하지 않도록 NoSchedule이라는 테인트가 설정된 상태이기 때문입니다. 앞서 설명한 테인트와 톨러레이션을 참고해주세요!
--set master.tolerations[0].key=node-role.kubernetes.io/master \
--set master.tolerations[0].effect=NoSchedule \
--set master.tolerations[0].operator=Exists \
# 젠킨스를 구동하는 파드가 실행될 떄 가질 유저 ID와 그룹ID를 설정합니다. 이때 사용되는 runAsUser는 사용자 ID를 의미하고, runAsGroup은 그룹 ID를 의미합니다.
--set master.runAsUser=1000 \
--set master.runAsGroup=1000 \
# 이후 젠킨스 버전에 따른 UI 변경을 막기 위해서 젠킨스 버전을 2.249.3으로 설정합니다.
--set master.tag=2.249.3-lts-centos7 \
# 차트로 생성되는 서비스의 타입을 로드밸런서로 설정해 외부 IP를 받아옵니다.
--set master.serviceType=LoadBalancer \
# 젠킨스가 http상에서 구동되도록 포트를 80으로 지정합니다.
--set master.servicePort=80 \
# 젠킨스에 추가로 필요한 설정들을 2~3번째 줄에 변수로 선언했습니다. 변수를 호출해 적용합니다.
--set master.jenkinsOpts="$jkopt1 $jkopt2" \
# 젠킨스를 구동하기 위한 환경 설정에 필요한 것들은 4~5번째 줄에 변수로 선언했습니다. 변수들을 호출해 젠킨스 실행 환경(JVM, 자바 가상 머산)에 적용합니다.
--set master.javaOpts="$jvopt1 $jvopt2"

여기까지 헬름을 이용해서 복잡한 젠킨스의 설치 과정을 간단하게 완료해봤습니다!

젠킨스 살펴보기

젠킨스를 직접 접속해서 살펴보기에 앞서 현재 설치된 젠킨스의 구조를 간단히 살펴보겠습니다.
젠킨스 컨트롤러는 마스터 노드에 설치했지만,
젠킨스 에이전트는 필요 시에 생성되고 작업을 마치면 삭제되는 임시적인 구조를 가집니다.
따라서 젠킨스 에이전트 작업 내용들은 삭제 전에 젠킨스 컨트롤러에 저장되어야 하며,
이를 위해 젠킨스 에이전트 서비스가 항상 독작하고 있습니다.
kubectl get services 명령으로 현재 젠킨스 에이전트 서비스를 확인할 수 있습니다.

젠킨스 컨트롤러를 단독으로 설치할 경우에는 컨트롤러가 설치된 서버에서
젠킨스 자체 시스템을 관리, CI/CD 설정, 빌드 등의 작업을 모두 젠킨스 컨트롤러 단일 노드에서 수행합니다.
하지만 컨트롤러-에이전트 구조로 설치할 경우 컨트롤러는 젠킨스 자체의 관리 및 CI/CD와 관련된 설정만을 담당하고
실제 빌드 작업은 에이전트로 설정된 노드에서 이루어집니다.

따라서 컨트롤러 단독 설치는 일반적으로 간단한 테스트에서만 사용되고 주로 컨트롤러-에이전트 구조로 사용하기 때문에 실습은 이와 같은 환경으로 구성했습니다.

그럼 젠킨스에 직접 접속해 어떻게 구성돼 있는지 살펴봅시다!

젠킨스 접속하기

호스트 웹 브라우저를 열고 앞서 확인한 로드밸런서 타입의 외부 IP인 192.168.1.11에 접속하면 다음과 같은 로그인 화면을 확인할 수 있습니다.

  • 사용자 이름 : 젠킨스에서 사용할 이름을 입력합니다. 앞서 설정한대로 사용자 이름은 admin입니다.
  • 비밀번호 : 젠킨스에 접속할 사용자 비밀번호를 입력합니다. 비밀번호는 랜덤으로 생성되나 편의를 위해 admin과 동일하게 admin으로 설정했습니다.

사용자 이름과 비밀번호 입력 후 로그인을 누르면 다음과 같이 젠킨스 홈 화면을 확인할 수 있습니다.
혹시 처음 접속할 때 오른쪽 상단에 빨간색 알람이 보인다면 이유는 젠킨스의 플러그인 업데이트가 필요하기 때문입니다.
이 에러를 해결하는 방법은 Jenkins 관리 메뉴를 알아보면서 살펴봅시다.

메인 화면 좌측에 있는 메뉴들은 다음과 같은 역할을 합니다.

  1. 새로운 Item

    • 젠킨스를 통해서 빌드할 작업을 Item이라고 합니다. 자세한 설명은 잠시 후에 다루겠습니다.
  2. 사람

    • 사용자는 관리하는 메뉴입니다. 현재는 최초 접속한 admin 사용자만 등록돼 있습니다.
    • 사용자의 정보를 관리하는 데는 젠킨스를 구동하는 서버에서 직접 사용자를 관리하는 방법과 젠킨스가 별도의 데이터베이스를 가지고 자체적으로 사용자를 관리하는 방법이 있습니다.
    • 별도의 데이터베이스가 없는 환경이기 때문에 현재는 직접 사용자를 관리하도록 구성돼 있습니다.
  3. 빌드 기록

    • 젠킨스 작업에 대한 성공, 실패, 진행 내역을 이곳에서 볼 수 있습니다.
  4. Jenkins 관리

    • 젠킨스의 시스템, 보안, 도구, 플러그인 등 각종 설정을 수행하는 곳입니다. 잠시 후 더 자세히 살펴봅니다.
  5. My Views

    • 젠킨스에서 각종 작업을 분류해 모아서 볼 수 있는 대시보드입니다.
  6. Lockable Resources

    • 젠킨스에선 한 번에 여러 개의 작업이 동시에 일어날 수 있습니다.
    • 이때 작업이 진행 중이라면 옵션에 따라 다른 작업은 대기를 해야 할 수 있습니다.
    • 이를 동시성 문제라고 하며 젠킨스에서는 작업이 끝날 때까지 같은 작업을 하지 못하게 하는 잠금 장치를 Lockable Resoucre로 설정할 수 있습니다.
  7. New View

    • 대시보드인 View를 생성하는 작업입니다.

젠킨스를 사용하려면 몇 가지 기본 설정이 필요합니다. Jenkins 관리 메뉴를 눌러 젠킨스 설정 화면에 대해서 알아보겠습니다.(자주 사용하는 메뉴들 중심으로)

  1. 의존 플러그인 버전

    • 오류 : 현재 사용하고 있는 플러그인이 의존하는 프러그인에 버전이 만족되지 않아 발생하는 문제를 알려주는 안내 창입니다.(이전 페이지에서 오른쪽 상단에 빨간 알림이 뜨셨던 분은 Jenkins 관리 창에 오류 창이 하나 떠있을 것입니다.)
    • 아래의 [4. 플러그인 관리 메뉴]로 의존하는 플러그인을 업데이트해 문제를 해결할 수 있습니다.
  2. 시스템 설정

    • 메인 화면에 표시될 문구, 동시에 실행할 수 있는 실행기(executors)의 개수, 젠킨스에 접속할 수 있는 경로, 관리자의 정보, 시스템 전체에 적용할 수 있는 환경변수, 시스템에서 공통적으로 활용해야 하는 플로그인 파일의 경로와 설정 정보 등을 이곳에서 설정할 수 있습니다.
  3. Global Tool Configuration

    • 빌드 과정에서 사용하는 도구(Maven, JDK, Git, Docker 등)의 경로 및 옵션을 설정할 수 있습니다.
    • 플러그인 관리를 통해 추가로 사용할 도구를 설정하면 이 메뉴에서 해당 도구를 설정하는 메뉴를 찾을 수 있습니다.
  4. 플러그인 관리

    • 젠킨스에서 사용할 플러그인을 설치, 삭제, 업데이트할 수 있습니다.
    • 젠킨스 홈 화면에서 보이는 알람은 여기서 플러그인을 업데이트해 해결할 수 있습니다.
  5. 노드 관리

    • 젠킨스에서 사용하는 노드를 추가, 삭제하거나 노드의 세부 설정 및 상태 모니터링을 할 수 있느 ㄴ메뉴입니다.
    • 젠킨스에서는 작업을 수행할 수 있는 각 단위를 쿠버네티스와 당일하게 노드라고 하며, 노드에 레이블을 붙여 관리하거나 노드의 동작 방식을 설정할 수 있습니다.
  6. Configuration as Code

    • 젠킨스의 설정을 내보내거나 불러올 수 있습니다.
    • 이 메뉴를 통해 다른 곳에서 구성한 젠킨스 설정을 옮겨오거나 내 젠킨스의 설정을 내보내 공유할 수 있습니다.
    • 새로운 젠킨스를 구성해 현재 젠킨스의 설정을 이전할 때 유용한 메뉴입니다.
  7. Manage Credentials

    • 젠킨스에서 사용하는 플러그인에 필요한 접근 키, 비밀 키, API 토큰과 같은 접속에 필요한 인증 정보를 관리합니다.
    • 노출이 되면 매우 곤란한 중요 정보이기 때문에 프로젝트에 직접 입력하지 않고 필요한 경우 호출해 사용합니다.
    • 이 기능을 이용해서 후에 사용하는 여러 플러그인들이 다른 프로그램으로 연결될 때 보관한 인증 정보를 넘겨줍니다.

젠킨스 알람이 갑자기 사라졌어요
젠킨스의 알람은 젠킨스 관리 메뉴에 진입하는 경우, 수정을 하고 있다고 판단해 알람을 보여주지 않습니다. 따라서 당장 알람이 사라졌다고 해서 문제가 수정된 것이 아니니 오해가 없길 바랍니다.

젠킨스 컨트롤러 설정하기

젠킨스를 사용하기 위해서는 기본적으로 컨트롤러와 에이전트 구동과 관련한 여러 설정이 필요합니다.
하지만 편의를 위해 컨트롤러와 에이전트에 대한 설정 중에 필요한 입력 부분을 헬름을 설치할 때 이미 포함시켰습니다.
미리 입력한 부분과 그 외 설정은 젠킨스를 관리하기 위해 알아두어야 하므로 어디서 설정이 가능한지 살펴보고 진행해봅시다!

젠킨스 시스템 설정하기

젠킨스 컨트롤러에 관한 설정을 진행하기 위해서 왼쪽 상단의 젠킨스 로고를 클릭해 홈 화면으로 이동한 후에 젠킨스 관리 > 시스템 설정 메뉴로 이동합니다.
다음과 같이 젠킨스 시스템 설정 페이지가 나옵니다.
여기서 설정되는 내용은 현재 접속한 컨트롤러의 설정을 의미합니다.

젠킨스 시스템 설정 메뉴에서는 다음과 같은 설정값을 제공합니다.

  1. 시스템 메세지

    • 젠킨스 메인 웹 페이지에 접속했을 때 나타나는 메세지를 입력합니다.
    • 이 메세지를 통해 사용자에게 젠킨스에 대한 소개나 간단한 안내를 할 수 있습니다.
  2. # of executors

    • 동시에 빌드를 수행할 수 있는 실행기의 개수를 설정하는 옵션입니다.
    • 이 옵션은 컨트롤러 노드에서 몇 개까지의 빌드를 실행할 수 있을지 설정할 수 있습니다.
    • 현재 설치된 젠킨스의 경우 에이전트 파드를 통해 빌드 작업을 생성하므로 이 옵션을 0으로 설정하는 것이 바람직합니다.
  3. Label

    • 노드를 구분할 수 있는 레이블을 지정합니다.
    • 이렇게 설정한 레이블을 통해 Usage 옵션을 사용하면 특정 작업을 어떤 노드에서 작업할지 결정할 수 있습니다.
  4. Usage

    • 젠킨스의 빌드 작업에 대해 젠킨스 노드가 어떻게 처리할지 설정합니다.
    • Use this node as much as possible(이 노드를 가능한 많이 사용) 옵션은 빌드 작업을 수행할 때 별도의 조건 없이 노드에 빌드를 할 수 있는 환경이라면 현재 노드에서 빌드를 진행하도록 설정하는 것입니다. 이러한 옵션은 일반적인 환경에서 빌드 작업에 적합합니다.
    • Only Build jobs with label expressions matching this node(이 노드와 일치하는 레이블 표현식을 가진 작업만 빌드) 옵션은 빌드와 대상의 레이블이 같아야 빌드를 할 수 있습니다. 주로 빌드 환경이 다른 플랫폼에서 빌드를 수행할 때 사용됩니다.
  5. Quiet period

    • 빌드 작업이 시작될 때까지 잠시 대기하는 시간을 설정하는 값입니다.
    • 단위는 초 단위이며, 짧은 시간에 변경된 코드에 대해서 중복으로 작업을 수행하지 않고 가장 마지막으로 변경된 코드를 빌드하기 위해 설정합니다.
  6. SCM checkout retry count

    • 소스 코드 저장소(SCM)로부터 파일을 가져오지 못한 경우 몇 번 재시도를 할지 설정하는 옵션입니다.
    • SCM이란 소스 코드 관리(Source Code Management)의 약자로 개발자들이 소스 코드를 통합하고 관리하며 이력을 추적하기 위해 사용하는 시스템을 의미합니다.
  1. Restrict project naming

    • 젠킨스를 통해 만들어지는 작업의 이름 규칙을 설정하는 옵션입니다.
    • 체크박스에 체크하면 이름 규칙을 편집할 수 있는 영역이 생기며 제약 조건은 정규식 패턴으로 작성해 적용할 수 있습니다.
    • 현재 설치된 젠킨스는 Strategy가 Default로 설정돼 있기 때문에 자유롭게 프로젝트 이름을 설정할 수 있습니다.
  2. Jenkins URL

    • 설치된 젠킨스 컨트롤러의 접속 주소입니다.
    • 앞서 헬름을 설치할 때 로드밸런서를 통해 설정될 IP인 192.168.1.11을 설정했습니다.
    • 이 주소는 젠킨스가 외부로 알림을 보내거나 자신의 주소를 알려줍니다.
  3. Resource Root URL

    • 빌드 결과물과 같은 내용을 외부에 공개하기 위해 사용되는 주소로 Jenkins URL과 다릅니다.
    • 이 실습에서는 빌드 결과물을 외부에 공개할 수 없는 가상 환경에 구성해 두었기 때문에 설정하지 않습니다.
    • 다만, 이후에 빌드 결과물을 공유하기 위해 설정하는 주소는 곧 실습해보도록 합시다.

젠킨스 시스템에서 설정할 수 있는 여러 내용을 알아봤습니다. 다음은 젠킨스에 기능을 불어넣는 플러그인을 살펴봅시다!

젠킨스 플러그인 관리하기

젠킨스는 실행되는 모든 기능을 플로그인으로 구현하도록 설계돼 있습니다.
이렇게 설치한 플러그인들을 단독으로 사용하거나 여러 개를 조합해 더 강력한 CI/CD 기능을 만들 수 있습니다.
이런 예로 쿠버네티스 위에 에이전트 파드를 설정할 수 있게 도와주는 메뉴는 kubernetes 플러그인이 있습니다.
잠시 후 젠킨스 사용하기에서 실습하게 될 파이프라인 프로젝트 기능도 플러그인을 통해서 구현됩니다.
혹시 아까 플러그인 오류가 떴다면, 지금 문제를 해결해보도록 합시다.

젠킨스 홈 화면에서 젠킨스 관리 > 플러그인 관리 메뉴로 이동합니다.

젠킨스 플러그인 관리 메뉴는 다음과 같은 기능을 제공합니다.

  1. 업데이트된 플러그인 목록

    • 젠킨스에 설치된 플러그인 중에 업데이트된 플러그인이 있는 경우 최신 버전으로 올릴 수 있습니다.
    • 이때 업데이트를 할 수 없는 플러그인은 보안 취약점이 발견됐거나 플러그인의 버전이 젠킨스 호환 버전이 아닌 경우입니다.
  2. 설치 가능

    • 설치되지 않은 플러그인을 검색해 현재 젠킨스에서 해당 기능을 추가할 수 있습니다. 아래 Gitpos 구현에서 이 기능을 사용합니다.
  3. 설치된 플러그인 목록

    • 현재 젠킨스에 설치돼 있는 플러그인 정보를 확인할 수 있으며, 더 이상 필요가 없어진 플러그인의 경우 이 페이지에서 제거할 수 있습니다.
  4. 고급

    • 외부와 연결되는 프록시 서버 설정을 할 수 있습니다.
    • 외부와 연결된 프록시 서버를 통해서 내부망에서도 젠킨스를 설치하고 업데이트할 수 있습니다.
    • 그 외에도 별도의 플러그인 파일을 업로드해 플러그인을 설치할 수 있습니다.

이제 젠킨스에서 사용해야 하는 플러그인을 업데이트하겠습니다.
화면을 맨 아래로 내리면 다음과 같은 화면이 보입니다.
선택 항목 중에서 호환 가능한(Compatible) 플러그인을 선택한 후, 지금 다운로그하고 재시작 후 설치하기 버튼을 눌러서 플러그인을 업데이트합니다.

  • 하단에 파란색 글씨를 잘 봐주세요! Compatible을 클릭하면 모든 호환 가능한 플러그인이 선택됩니다. 한꺼번에 업데이트 하도록 합시다~

젠킨스 플러그인을 업그레이드하는 경우엔 재시작이 필요합니다.
따라서 설치가 끝나고 실행 중인 작업이 없으면 Jenkins 재시작을 체크해 작업 종류 후에 젠킨스가 재시작되게 합니다.

설치가 끝나면 다시 로그인해 홈 화면에 접속합니다.
다음 그림처럼 오른쪽 상단에 모든 알림이 사라진 것을 확인할 수 있습니다.

젠킨스 컨트롤러(마스터 노드)에 대한 설정을 모두 마쳤습니다. 다음으로 젠킨스 에이전트(워커 노드)를 설정해 봅시다.

젠킨스 에이전트 설정하기

젠킨스 에이전트도 미리 설정해뒀지만, 입력된 설정들의 위치와 목적을 이해하고 넘어갑시다.

젠킨스 노드 관리

홈 화면에서 젠킨스 관리 > 노드 관리 메뉴로 이동합니다.

노드 관리의 주요 내용은 다음과 같습니다.

  1. 신규 노드

    • 에이전트 노드를 추가합니다.
    • 고정된 여러 대의 서버에서 에이전트 노드를 추가해야할 때 필요합니다.
  2. Configure Clouds

    • 클라우드 환경 기반의 에이전트를 설정할 때 필요합니다.
    • 쿠버네티스 위에 설치된 젠킨스의 에이전트에 관한 설정도 이 메뉴에서 설정할 수 있습니다.
  3. Node Monitoring

    • 에이전트 노드의 안정성을 위한 각종 모니터링과 관련된 사항을 설정할 수 있습니다.
  4. 노드 목록

    • 현재 구성된 노드 목록을 보여줍니다.
    • 쿠버네티스상에 설치한 젠킨스는 작업이 진행될 때만 파드 형태의 에이전트가 생성되고 작업이 끝나면 파드가 사라지기 때문에 작업 중이 아니라면 이 목록에는 젠킨스 컨트롤러 노드만 표시됩니다.

쿠버네티스에서 젠킨스 에이전트 구성

Configure Clouds 메뉴로 이동합니다.
이미 많은 내용이 입력돼 있는데, 헬름을 통해 젠킨스를 설치할 때 JCasC(Jenkins Configuration as Code)라는 기능을 사용해, 현재 쿠버네티스 환경에 맞게 많은 설정을 자동화했기 때문입니다.
따라서 사용자는 일부만 수정하면 됩니다.
이런 과정은 kubernetes 플러그인의 도움을 받아서 진행되므로 앞에서 플러그인 업데이트를 먼저 진행했습니다.

Configure Clouds 메뉴의 주요 내용을 살펴봅시다.

  1. Kubernetes

    • 쿠버네티스 설정과 관련된 영역입니다. Name에 이름을 지정할 수 있습니다.
  2. Kubernetes Cloud details

    • 쿠버네티스 클러스터에 접속하기 위한 정보를 설정할 수 있습니다.
    • 헬름을 통해서 쿠버네티스 위에 설치한 젠킨스는 쿠버네티스 클러스터 내부에서 동작하기 때문에 기본값으로 둬도 무방합니다.
    • 쿠버네티스 클러스터 외부에 젠킨스를 설치한 경우에는 이곳에서 쿠버네티스에 대한 정보를 수정해야 합니다.
  3. Pod templates

    • 쿠버네티스 위에 설치된 젠킨스는 작업 시 에이전트를 파드의 형태로 생성합니다.
    • 이곳에서 에이전트로 사용할 파드와 관련된 설정을 합니다.
    • 이때 Pod Template은 젠킨스 컨트롤러를 다시 시작하면 모든 설정이 초기화됩니다. 따라서 현재 환경에서 마스터 노드를 다시 시작하면 모든 설정이 초기화됩니다.
    • 이를 해결하기 위해 헬름 설치 시에 미리 구성한 설정값(jenkins-config.yaml)을 읽어 들이도록 구성했습니다.
    • 젠킨스 에이전트에 대한 설정은 현재 기본 Template인 default에 작성돼 있습니다. 그리고 이에 대한 설정값을 미리 헬름을 통해 입력받았습니다. 따라서 어떤 부분들이 변경됐는지 코드와 함께 살펴보겠습니다.
    • 세부 내용을 보기 위해서 Pod Templates... 버튼을 눌러 메뉴를 펼치고 화면에 보이는 기본 Template인 default의 메뉴 내부로 Pod Template details... 버튼을 다시 누릅니다.

젠킨스 에이전트 템플릿의 상세 내용

내용을 살펴보기 전에 잠깐 현재 작업에 대한 목적을 간단히 살펴보겠습니다.
젠킨스의 CI/CD 작업은 실제로 에이전트로 동작하는데, 쿠버네티스 환경에서는 에이전트가 파드로 운영되나 이 파드에는 도커 빌드를 위한 docker 명령과 쿠버네티스 배포를 위한 kubectl 명령이 존재하지 않습니다.
가장 쉬운 해결 방법은 호스트 시스템에 있는 도커와 kubectl을 그대로 이용하는 것입니다.
따라서 hostpath를 잡아 각 노드에 이미 설치돼 있는 도커와 kubectl을 그대로 이용하겠습니다.
여기서 hostpath란 쿠버네티스 파드에서 워커 노드에 있는 특정 경로를 마운트해서 파드 내에서 사용할 수 있는 것을 말합니다!

편의를 위해서 미리 설정된 내용이 default pod Template에 이미 적용돼 있습니다.

추가로 default pod Template을 살펴보기 전에 알아두면 이해하는데 도움이 되는 내용을 언급하고 넘어가겠습니다.
Pod Template의 내용을 여러 번 보다 보면 어디선가 보던 구조임을 알 수 잇습니다.
Pod Template은 말 그대로 파드의 구성 요소를 그대로 메뉴상에 넣어 둔 것입니다.
상당히 많은 내용이 있으나 실제로는 파드 생성에 필요한 정보들을 그대로 메뉴로 구현한 것입니다.

해당 내용을 생성된 임의의 파드를 선택해서 kubectl get pod <이름> -o yaml로 나온 결과를 지금부터 진행행 Pod Template과 비교해 보면 훨씬 더 쉽게 이해가 될 겁니다.
그러면 기본적인 Pod Template 정보 및 jenkins-config.yaml을 통해 입력된 내용을 살펴봅시다!

  • kubectl get pods
  • kubectl get pod jenkins-6b6f9b8675-9j2mf -o yaml

Pod Template 이름을 설정할 수 있는 페이지가 가장 위에 있습니다.

  1. Name

    • Pod Template의 이름을 설정할 수 잇습니다.
  2. Labels

    • 에이전트 노드를 구분할 때 사용할 레이블을 설정할 수 있습니다.
    • 여기서 설정하는 레이블은 pod metadata에 label을 설정하는 것과 동일합니다.
  3. Usage

    • 노드의 사용 방법(Usage)를 설정할 수 있으며 젠킨스 컨트롤러와 마찬가지로 Use this node as much as possible(이 노드를 가능한 많이 사용)인 기본 설정을 그대로 사용하겠습니다.

Pod Template에 파드에 대한 기본 정보를 입력했으니 파드에서 사용할 컨테이너 설정을 진행하겠습니다.

파드에서 사용할 컨테이너를 설정하는 메뉴입니다.

  1. Name

    • 컨테이너를 구분하기 위한 이름입니다.
  2. Docker image

    • 컨테이너에서 사용할 이미지를 지정합니다.
    • 이미지는 기본 설정대로 젠킨스에서 제공하는 jenkins/inbound-agent:4.3-4를 사용하겠습니다.
  3. Command to run

    • 여기에 적혀진 명령은 컨테이너에서 실행하는 명령이 됩니다.
    • 기존에 실행하는 명령 위에 덮어쓰는 구조로 컨테이너의 의도와 다르게 강제 실행을 위한 명령이 있는 경우 사용될 수 있습니다.
    • 하지만 젠킨스 에이전트로 동작하는 파드의 경우 컨테이너는 젠킨스에서 의도한 대로 동작해야하기 때문에 빈칸으로 설정합니다.
  4. Environment Variable

    • 컨테이너의 환경변수를 설정하는 곳입니다.
    • 이전에 젠킨스 컨트롤러 설정하기에서 설정한 JENKINS_URL을 Environment Variable 영역에도 동일하게 http://192.168.1.11로 설정했습니다.
    • 이렇게 바뀐 JENKINS_URL은 이후 젠킨스 플러그인으로 구현하는 Gitops 실습에서 사용됩니다.

다음으로 빌드 작업 중 호스트에 설치된 명령어를 파드 내부에서 사용하기 위한 Volumes를 설정하는 메뉴를 살펴봅시다!
스크롤을 조금 내린 후 Add Volume을 선택하면 다음과 같은 화면이 나옵니다.

파드 내부에 볼륨(volume)을 설정할 수 있는 여러 가지 옵션을 제공합니다.

  1. Config Map Volume

    • 쿠버네티스에 존재하는 ConfigMap 오브젝트를 파드 내부에 연결해 이를 파드에서 사용할 수 있도록 합니다.
  2. Empty Dir Volume

    • 파일 및 내용이 없는 디렉터리를 파드 내부에 생성합니다.
    • 젠킨스로 빌드할 때 컨테이너가 여러 개 생성될 수 있는데, 이런 경우 컨테이너 간에 공유할 수 있는 디렉터리로 사용할 볼륨으로 Empty Dir을 주로 사용합니다.
  3. Host Path Volume

    • 호스트, 즉 쿠버네티스 워커 노드에 파일 및 디렉터리를 파드에서 사용할 수 있도록 연결해 줍니다.
    • 이를 통해 파드는 호스트에 위치한 명령이나 데이터를 사용할 수 있으며, 필요한 경우 파일을 저장해 파드가 사라진 경우에도 데이터를 보존할 수 있습니다.
    • 실습에서는 이 볼륨을 사용해 파드에 필요한 실행 파일을 호스트에서 가져와서 사용합니다.
  4. NFS Volume

    • NFS 서버에 위치한 원격의 디렉터리를 파드가 사용할 수 있도록 합니다.
  5. Persistent Volume Claim

    • 쿠버네티스 클러스터에서 PVC로 설정한 볼륨을 파드에서 사용할 수 있도록 합니다.
  6. Secret Volume

    • 쿠버네티스에 있는 Secret 오브젝트를 파드 내부에 연결해 파드에서 사용할 수 있도록 합니다.

젠킨스를 이용한 배포 작업은 내부에서 셸 스크립트 단위로 작업을 나누어 구성할 수 잇습니다.
우리의 목적은 젠킨스를 이용해 컨테이너 이미지를 빌드하고
컨테이너를 쿠버네티스에 배포하는 것입니다.
이를 위해 젠킨스 내부에서 kubectl, docker와 같은 명령어를 사용해야 합니다.
하지만 배포되는 파드는 이와 같은 명령들이 포함돼 있지 않은 도커 이미지이기 떄문에
호스트에 존재하는 명령을 파드에서 그대로 사용할 수 있는 Host Path Volume을 사용해 구성했습니다.
구조적으로는 Host path(쿠바네티스 워커 노드)에 있는 내용이 Mount Path(젠킨스 에이전트 파드)로 설정되는 구조이며,
Host Path Volume으로 추가된 3개의 Host Path Volume은 다음과 같습니다.

  1. (kubectl) Host Path Volume

    • kubectl 명령을 에이전트 파드 내부에서 사용할 수 있도록 /usr/bin/kubectl 경로를 호스트로부터 연결해 줍니다. 이를 통해 빌드 작업 중 쿠버네티스와 관련된 작업을 할 수 있습니다.
  2. (docker) Host Path Volume

    • docker 명령을 에어전트 파드 내부에서 사용할 수 있도록 /bin/docker 경로를 호스트로부터 연결해 줍니다. 이를 통해 빌드 작업 중 도커 이미지를 생성하고 저장소로 밀어 넣을 수 있습니다.
  3. (docker.sock) Host Path Volume

    • kubectl과 API 서버가 통신하는 것처럼 도커도 도커 데몬과 통신하기 위해서 API 서버 역할을 하는 docker.sock이 있습니다. 따라서 이미 호스트에 설치된 /var/run/docker.sock 소켓을 에이전트 파드에 사용하도록 설정해줍니다.

스크롤을 더 내려가다 보면 젠킨스 에이전트 파드에서 사용할 서비스 어카운트(Service Account)와 사용자 ID 및 그룹 ID를 설정했습니다.

메뉴 내용은 다음과 같습니다.

  1. 서비스 어카운트

    • 쿠버네티스 클러스터 및 오브젝트의 정보를 조회하기 위한 계정입니다.
    • 젠킨스에 접속하기 위한 admin 계정과 같은 개념입니다.
    • 젠킨스의 에이전트 파트는 jenkins라는 서비스 어카운트를 사용합니다.
  2. 사용자 ID

    • 에이전트 파드가 실행될 때 파드에 부여되는 숫자로, 리눅스 사용자에게 부여되는 숫자 식별자입니다.
    • 여기에서는 에이전트 파드가 루트 권한을 가진 사용자 ID를 사용하지 않게 하기 위해서 사용자 ID에 대한 값은 1000으로 설정합니다.
  3. 그룹 ID

    • 에이전트 파드가 실행될 때 파드에 부여되는 숫자로 리눅스 사용자에게 부여되는 숫자로 된 식별자입니다.
    • 관용적으로 리눅스에서 사용되는 0부터 500까지의 ID는 리눅스 시스템이 사용하는 ID입니다.
    • 여기서는 에이전트 파드가 시스템이 사용하는 ID를 쓰지 않고 독립적으로 컨테이너를 구동할 수 있게 하기 위해 993으로 설정했습니다.

이상으로 모든 입력 및 권한 설정을 마치면 하단의 Save를 눌러 저장합니다!

쿠버네티스 API 서버와의 통신은 단순히 서비스 아카운트를 설정하고 이에 맞는 사용자 ID 및 그룹 ID를 갖는다고 해서 가능하 것은 아닙니다.
서비스 어카운트에 쿠버네티스 API 서버와의 통신 권한을 따로 적용해야 합니다.
아래에서 진행해봅시다!

젠킨스 서비스 어카운트를 위한 권한 설정하기

실제로 젠킨스 에이전트 파드에서 쿠버네티스 API 서버로 통신하려면 서비스 어카운트에 권한을 줘야 합니다.
권한을 주기 전에 우선 jenkins 서비스 어카운트가 존재하는지 kubectl get serviceaccounts로 확인합니다.

이제 서비스 어카운트 계정인 jenkins에 쿠버네티스 클러스터에 대한 admin 권한을 부여합니다.

  • kubectl create clusterrolebinding jenkins-cluster-admin \ --clusterrole=cluster-admin --serviceaccount=default:jenkins

권한이 적용되었씁니다! 권한을 부여한 의미를 알아봅시다.

jenkins 서비스 어카운트를 통해 젠킨스 에이전트 파드를 생성하거나 젠킨스 에이전트 파드 내부에서 쿠버네티스의 오브젝트에 제약 없이 접근하려면 cluster-admin 역할(role) 을 부여해야 합니다.
필요한 영역으로 나누어 권한을 부여하는 것이 일반적이나 효율적으로 실습하기 위해 cluster-admin 1개 권한만 부여하였습니다.
서비스 어카운트에 cluster-admin 역할을 부여하고 이를 권한이 필요한 서비스 어카운트(사용자, 그룹)인
jenkins에 묶어(Binding) 줍니다.
이런 방식을 역할 기반 접근 제어(RBAC, Role-Based Access Control)라고 합니다.

쿠버네티스의 역할 부여 구조

쿠버네티스의 역할 부여 구조는 할 수 있는 일(무엇을 할 수 있는가?)할 수 있는 주체(누가 할 수 있는가?) 의 결합으로 이루어 집니다.

  1. Rules(규칙)

    • 역할 기반 접근 제어에서 '할 수 있는 일'과 관련된 Role, ClusterRole이 가지고 있는 자세한 행동 규칙 입니다.
    • Rules는 apiGroups, resources, verbs의 속성을 가집니다.
    • 쿠버네티스 클러스터상에서 어떤 행동을 한다는 것을 구체적으로 살펴보면 특정 API를 통해서 어떠한 자원에 접근해 목록이나 정보를 조회하거나, 자원을 생성, 삭제, 수정하는 등의 행위를 하는 것을 의미합니다.
    • 접근할 수 있는 API의 집합은 Rules에서 apiGroups로 표현할 수 있고, API 그룹에 분류된 자원 중 접근 가능한 자원을 선별하기 위해 resources를 사용합니다.
    • 접근할 수 있는 자원이 정의됐다면 해당 자원에 대해서 할 수 있는 행동을 규정하기 위해 verbs를 사용할 수 있습니다.
    • 이 행동의 종류는 get(정보 얻기), list(목록 조회), create(자원 생성), update(자원 갱신), patch(일부 수정), watch(감시), delete(삭제)가 있습니다.
    • 만약 해당 자원의 정보 얻기 및 목록 조회만이 가능한 규칙을 설정하기 위해서는 get과 list만을 verbs에 추가하면 됩니다.
  2. Role, ClusterRole(역할)

    • '할 수 있는 일'을 대표하는 오브젝트입니다. 앞에서 설명한 Rules에 적용된 규칙에 따른 동작을 할 수 있으며 적용 범위에 따라 Role과 ClusterRole로 나뉩니다.
    • Role은 해당 Role을 가진 주체가 특정 namespace에 대해서 접근할 수 있습니다.
    • ClusterRole은 해당 ClusterRole을 가진 주체가 쿠버네티스 클러스터 전체에 대해서 접근할 수 있도록 합니다.
  3. RoleBinding, ClusterRoleBinding

    • 이 오브젝트는 Role과 ClusterRole이 대표하는 '할 수 있는 일' 속성을 '할 수 있는 주체'를 대표하는 속성인 Subjects와 연결시켜 주는 역할을 합니다.
    • Role와 ClusterRole은 공통적으로 roleRef(할 수 있는 역할의 참조)와 subjects(수행 주체)라는 속성을 가지고 있으며, 이 두가지가 결합하며 역할 기반 접근 제어를 수행하게 됩니다.
    • RoleBinding은 앞에서 설명한 Role과 결합하여 네임스페이스 범위의 접근제어를 수행합니다.
    • ClusterRoleBinding은 ClusterRole과 결합해 클러스터 전체 범위의 접근 제어를 수행합니다.
  4. Subjects

    • 역할 기반 접근 제어에서 행위를 수행하는 주체르를 의미합니다.
    • Subjects는 특정 사용자 혹은 그룹, 서비스 어카운트를 속성으로 가질 수 있습니다.
    • 사용자란 쿠버네티스에 접근을 수행하는 실제 사용자를 의미합니다. 쿠버네티스 클러스터에 등록된 사용자의 목록은 kubeconfig의 users 섹션에 기록돼 있습니다.
    • 서비스 어카운트는 파드 내부의 프로세스에 적용되는 개념입니다.
    • 파드는 네임스페이스에 존재하는 default 서비스 어카운트를 사용하거나 특정한 서비스 어카운트를 사용하도록 설정할 수 있으며, 파드 내부의 프로세스는 설정된 서비스 어카운트로서 쿠버네티스상에 존재하는 자원에 접근을 시도할 수 있습니다.

앞에서 실행했던 kubectl create clusterrolebinding jenkins-cluster-admin \ --clusterrole=cluster-admin --serviceaccount=default:jenkins의 의미를 다시 한 번 살펴봅시다.
kubectl create를 통해서 clusterrolebinding을 jenkins-cluster-admin이라는 이름으로 만듭니다.
그리고 두 가지 옵션을 주는데, 첫 번째는 clusterrole에 묶여질 역할을 'cluster-admin이라는 미리 정의된 클러스터 관리자 역할'으로 줍니다.
두 번째 옵션은 jenkins-cluster-admin이라는 클러스터 역할의 서비스 어카운트를 jenkins로 지정합니다.
이때 여러 가지의 서비스 어카운트가 존재할 수 있으므로 jenkins에 속해 있는 네임스페이스 default도 함께 지정합니다.
이를 표로 표현하면 다음과 같습니다.

명령설명
kubectl create오브젝트를 생성하는 kubectl 명령입니다.
clusterrolebinding생성되는 오브젝트가 clusterrolebinding임을 나타냅니다.
jenkins-cluster-adminclusterrolebinding으로 생성되는 오브젝트의 이름이 jenkins-cluster-admin임을 나타냅니다.
--clusterrole=cluster-adminclusterrolebinding의 첫 번째 옵션으로, cluster-admin 역할을 부여합니다.
--serviceaccount=default:jenkinsclusterrolebinding의 두 번째 옵션으로, default에 있는 jenkins라는 서비스 어카운트에 이 권한을 부여합니다.

이렇게 적용된 내용을 확인하기 위해서는 clusterrolebinding에 적용된 내용을 자세히 yaml로 출력해 보는 것입니다.
확인하고자 하는 내용은 가장 하단에 있습니다.

  • kubectl get clusterrolebindings jenkins-cluster-admin -o yaml
  • (roleRef) kind: ClusterRole, name: cluster-admin
  • (subjects) kind: ServiceAccount, name: jenkins

다소 내용이 복잡해 보이지만, 여러 번 생성을 반복하다보면 익숙해질겁니다.
역할 기반 접근 제어 기법은 쿠버네티스 뿐만 아니라 클라우드, 그리고 거의 모든 권한 제어에 표준처럼 쓰이는 기법이므로 기번 기회에 충분히 학습해 내 것으로 만들길 권장드립니다.

이렇게 권한이 설정된 jenkins 서비스 어카운트는 다음 그림과 같이 자유롭게 kubectl을 사용해 CI/CD를 쿠버네티스 내에서 구현할 수 있습니다.

지금까지 젠킨스를 헬름으로 설치하고 젠킨스의 기본적인 메뉴에 대해서 알아봤습니다.
그리고 CI/CD를 제공하는 젠킨스 컨트롤러와 에이전트를 설정해 CI/CD 파이프라인을 실행할 수 있는 모든 준비를 마쳤습니다.
일반적으로는 간단한 테스트를 돌려서 설치가 정상적으로 이루어졌는지 점검하나, 젠킨스의 간단한 테스트 자체는 큰 의미가 없기 때문에 바로 CI/CD를 사용해 보면서 설정에 문제가 없음을 검증해봅시다.
만약 이후 빌드 작업에서 오류가 발생한다면 앞서 실습한 학습한 내용들을 다시 점검하시기 바랍니다.


젠킨스로 CI/CD 구현하기

쿠버네티스 환경에서 젠킨스를 사용하기 위한 설정을 모두 마쳤으니 이제 젠킨스를 이용해서 CI/CD를 구성해봅시다!

상단의 Jenkins 로고를 클릭해 젠킨스 홈 화면으로 돌아간 후 새로은 Item을 클릭해 새로운 아이템을 만듭니다.

  • 혹시 젠킨스 홈페이지를 들어가는 방법을 까먹으셨나요?
    - 앞서 쿠버네티스에서 로드밸런서로 노출한 외부 IP인 192.168.1.11을 통해 브라우저에서 접근하면 됩니다.

젠킨스에서 아이템이란?

젠킨스에서 아이템이란 '새롭게 정의할 작업'을 의미합니다.
젠킨스가 CI/CD 도구임을 익히 들어서 알고 있지만, 사실 CI/CD 작업을 하려면 각각의 작업은 모두 정의가 필요합니다.
만약 작업을 코드로 정의한 경우라고 해도 작업 순서 정도는 알려줘야 합니다.

모든 작업의 정의와 순서를 모아 둔 전체 작업을 프로젝트라고 하는데, 프로젝트를 생성하는 방식은 Freestyle, Pipeline 등이 있습니다.
이렇게 프로젝트를 정의하고 생성하는 것을 아이템이라고 하며, 프로젝트 외에 실제로 작업에 도움이 되는 내용들을 정의하는 것도 아이템을 생성한다고 할 수 있습니다.

예를 들면 프로젝트를 구분해 저장해 두는 디렉터리인 Folder가 그러합니다.
이러한 용어를 몰라도 실습하는데 큰 지장은 없지만 추후 설명은 읽다보면 용어에 혼동이 올 수 있기 때문에
정확하게 정의하고 넘어갑시다.

화면에 있는 각 아이템은 다음과 같은 작업을 할 수 있습니다.

  1. Freestyle project

    • 스타일의 자유도가 높은 방식으로, 브라우저에서 사용자가 직접 설정값과 수행할 동작을 입력할 수 있습니다.
    • 화면에 보이는 항목을 입력하면서 구성할 수 있으서 젠킨스와 관련된 경험이 부족한 사용자도 구성하기 쉽다는 장점이 있습니다.
    • 하지만 과정이 복잡한 작업을 구성하기 어렵고, freestyle로 생성한 아이템은 입력한 항목의 명세서를 별도로 저장하는 과정이 없으므로 작성한 내용은 공유하기 어렵습니다.
  2. Pipeline

    • 젠킨스에서 지원하는 고유의 Pipeline 문법으로 코드를 작성해 작업을 정의하는 프로젝트입니다.
    • Freestyle과 비교해 문법을 사전에 숙지해야 한다는 점 때문에 비교적 진입 장벽이 있습니다.
    • 그렇지만 변수 정의, 반복문, 조건문 등의 프로그래밍 기법을 사용할 수 있어 좀 더 복잡한 방식의 작업을 정의하는 것이 가능합니다.
    • 또한 작성한 코드를 통해 새로운 프로젝트를 바로 생성할 수 있고 코드를 일부만 수정해 재사용하기도 수월합니다.
    • 깃허브와 같은 코드 저장소에 애플리케이션 코드를 올릴 때 Pipeline 코드로 작성한 파일을 함께 올려 두면 애플리케이션 코드와 배포 방법을 함께 관리할 수 있어 관리 편의성이 높습니다.
  3. Multi-configuration porject

    • 하나의 소스 코드를 여러 조건의 조합으로 나온 경우의 수에 해당하는 환경에 동시에 배포하는 프로젝트입니다.
  4. Folder

    • 젠킨스의 작업이 늘어나면 늘어날수록 단순하게 관리하기 어려운데 이런 경우 관련 작업들을 분류해 둘 필요가 있습니다. 이럴 경우 분류 가능한 디렉터리를 생성하는 것이 Folder입니다.
  5. Multibranch Pipeline

    • 하나의 소스 코드 저장소 내에 존재하는 각 브랜치에서 젠킨스 파이프라인 코드가 작성된 파일을 불러와 한 번에 여러 브랜치에 대한 품질 검증, 테스트, 빌드 등의 작업을 할 수 있도록 해줍니다.

5개의 아이템 중에서 주로 사용되는 것은 Freestyle과 Pipeline 입니다.
일반적으로 간단한 작업이나 공유보다는 직접 사용을 염두에 둔 작업은 프리스타일로 정의하고,
복잡도가 높아서 고려할 것이 많거나 정의해 둔 내용의 공유 및 재사용을 중시하는 경우에는 파이프라인을 사용합니다.

프리스타일과 파이프라인을 사용해 CI/CD를 구현해 보겠습니다!

Freestyle로 간단히 echo-ip 배포하기

먼저 Freestyle을 이용해 컨테이너를 쿠버네티스 클러스터에 배포하겠습니다.
젠킨스는 배포를 위해 사용하는 도구이므로 배포할 대상은 사전에 구성돼 있어야 합니다.
배포할 대상은 IP주소를 반환하는 간단한 Nginx 웹 서버인 echo-ip입니다.(쿠버네티스를 실습하면서 계속 썼던 서버인 것을 기억하시죠?)
CI를 실습하려면 echo-ip를 빌드해야 하므로 해당 소스를 깃허브 저장소(https://github.com/iac-source/echo-ip)에서 가져 옵니다.
그리고 CD를 위해서 kubectl create와 expose를 Freestyle 프로젝트에서 배포로 사용하도록 정의하겠습니다.
echo-ip는 앞으로 계속 실습에서 사용할 내용이니 간단히 살펴보도록 합시다.

  1. Dockerfile

    • echo-ip 도커 이미지를 빌드하는 데 사용하는 파일입니다. 도커 이미지를 만들기 위한 기초 이미지는 nginx:stable을 사용하고, 인증과 관련된 파일과 설정 파일을 복사한 후에 실행하도록 구성돼 있습니다.
  2. Jenkinsfile

    • Pipeline 실습을 위해 작성된 파일입니다.(Pipeline은 젠킨스만의 문법을 사용한 코드를 이용한다고 했죠?)
  3. README.md

    • 깃허브 초기 화면에서 안내 메세지를 표시하는 파일입니다.
  4. cert.crt

    • echo-ip의 Nginx에서 HTTPS 접속 시 사용하는 인증서 파일입니다.
  5. cert.key

    • echo-ip의 Nginx에서 HTTPS 접속 시 사용하는 비밀 키 파일입니다.
  6. nginx.conf

    • echo-ip의 응답을 설정하기 위한 Nginx 설정 파일입니다.
    • 해당 레포지터리의 이 설정 파일은 접속자의 IP 주소를 응답할 수 있는 간단한 설정을 포함하고 있습니다.

실습을 진행하기 전에 간략히 젠킨스 Freestyle로 CI/CD를 구성하는 순서도 요약해보겠습니다.

  1. 깃허브에서 echo-ip를 빌드할 정보가 담긴 파일들을 내려(pull) 받습니다.
  2. 받은 파일들을 이용해서 컨테이너 이미지를 빌드합니다.
  3. 빌드한 이미지를 레지스트리(192.168.1.10:8443)에 저장(push) 합니다.(레지스트리는 앞선 포스팅에서 구성하였습니다.)
  4. 레지스트리에 저장한 이미지를 쿠버네티스 클러스터에 디플로이먼트로 생성(kubectl create)하고 로드밸런서 서비스로 노출(kubectl expose)합니다.

작업 중 화면을 길게 방치하면 403 오류가 발생하는 이유
젠킨스에서 설정값을 입력하는 시간이 30분 이상 소요되거나 화면을 장시간 방치했을 때 정보를 제출하거나 화면을 전환하는 과정에서 403 No vaild crumb 오류가 발생하는 경우가 있습니다.
이는 젠킨스의 강화된 보안 때문입니다.
젠킨스는 2.222.1 버전부터 사이트 간 요청 위조(CSRF, Cross site request forgery) 공격을 방어하는 옵션을 강제로 활성화하며 임의로 이 옵션을 해제할 수 없습니다.

  • CORS, XSS, CSRF에 대한 포스팅
    사이트 간 요청 위조(CSRF)는 인증 받은 유저의 권한을 도용해 임의로 사이트의 특정 기능을 실행하는 공격 기법입니다.
    젠킨스는 이러한 공격을 방어하기 위해 서버로 정보를 전달하기 전 일정 시간 동안만 우효한 crumb이라는 토큰을 정보 입력 화면 진입 시에 발급합니다.(세션 같은 개념일 듯 하네요.)
    화면을 오랫동안 방치하면 이 토큰의 유효 기간이 만료돼 젠킨스 입장에서는 사용자가 서버로 전송한 내용이 공격 시도인지 정상적인 입력인지 판단하지 못해 오류를 발생시킵니다.
    따라서 한 화면 내에서 입력하는 시간은 30분 이내로 유지하거나, crumb 오류가 발생하면 당황하지 말고 새로고침 후에 다시 접속하면 됩니다.
    실험을 위해 30분 이상 접속을 유지하되 작업하지 않은 뒤 프로젝트를 생성하려고 했더니 저는 자동으로 로그아웃이 되어 오류를 발생시키는군요!(새로고침하면 재로그인 됩니다.)

자, 이제 설명은 끝났으니 앞의 작업 순대로 Freestyle 프로젝트를 진행해 봅시다~!

  1. 이름은dpy-fs-dir-prod로 지정하고 Freestyle project 아이템을 선택한 후 OK버튼을 누릅니다.

  2. General 탭에서 Restrict where this project can be run(이 프로젝트를 실행할 수 있는 위치 제한) 체크를 해체합니다.

    • 이 설정은 젠킨스의 에이전트가 특정한 레이블을 가지고 있을 때 해당 레이블을 가진 에이전트에서만 실행될 수 있도록 제한을 가하는 옵션인데, 현재는 불필요합니다.
  3. 소스 코드 관리 탭에서는 젠킨스 외부에 있는 소스 코드 저장소를 젠킨스 CI로 사용하도록 지정할 수 있습니다.

    • 설정을 위해 Git을 클릭해 그림과 같이 저장소 외에 항목들을 입력할 수 있는 메뉴로 변경된 것을 확인합니다.
    • 그리고 Repository URL에는 https://github.com/iac-source/echo-ip를 입력하고, Branch Specifier에는 현재 입력한 깃허브 저장소에 존재하는 주요(main) 브랜치 변경 내용에 대해서는 CI를 진행하도록 */master에서 */main으로 변경합니다. 그 외의 메뉴는 변경하지 않고 그대로 진행하겠습니다.
  4. Build 단계를 추가합니다. 실제로 젠킨스가 작업을 수행할 방법을 선택하는 단계입니다.

    • Add build step을 클릭해 Execute Shell을 선택합니다.
    • 이 항목에 입력한 셸 명령어로 빌드 작업이 수행됩니다.
  5. 젠킨스에서 빌드에 사용할 명령어를 확인하고 입력합니다.

    • 명령어는 도커 이미지 빌드, 도커 이미지 푸시, 디플로이먼트 생성, 로드밸런서를 통한 노출의 4단계로 이우러져 있습니다.
    • 실제로 젠킨스가 작업을 수행할 때는 위에 입력한 셸 명령어들이 수행됩니다.
    • 이 스크립트는 사전에 구성돼 있으므로 cat 명령으로 스크립트를 출력한 후 복사한 다음 Execute Shell 메뉴를 누른 후에 나온 칸에 붙여 넣습니다. 그리고 저장을 눌러 설정한 프로젝트 내용을 저장합니다.
    • cat ~/_Book_k8sInfra/ch5/5.4.1/echo-ip-101.freestyle
      docker build -t 192.168.1.10:8443/echo-ip . # 도커 빌드 / CI 작업
      docker push 192.168.1.10:8443/echo-ip # 도커 이미지 저장 / CI 작업
      kubectl create deployment fs-echo-ip --image=192.168.1.10:8443/echo-ip # 쿠버네티스 디플로이먼트 배포 / CD 작업
      kubectl expose deployment fs-echo-ip --type=LoadBalancer --name=fs-echo-ip-svc --port=8080 --target-port=80 # 쿠버네티스 서비스 노출 / CD 작업
  6. 저장 버튼을 누르면 그림과 같이 프로젝트 화면으로 돌아갑니다. Build Now를 눌러 저장한 프로젝트를 실행합니다.

  7. CI/CD 작업을 수행하면 Build History에 작업이 추가됩니다.

    • 작업이 정상적으로 종료됐다면 파란색 표시를 볼 수 있습니다.
    • 작업이 실패했다면 빨간색 표시가 나타납니다.


  8. 성공적으로 CI/CD 작업이 수행돼 파란색 원이 생성됐는지 확인합니다.

    • 작업 내용을 자세히 확인하고 싶다면 파란색 원 뒤에 #1을 누르고 #1(첫 번째) 빌드 작업 관련 내용을 살펴보는 메뉴로 이동합니다.
    • 빨간색 원이라면 CI/CD 중에 문제가 발생한 것으로 이와 동일한 과정을 거친 뒤에 빌드 작업과 관련된 내용을 확인하고 문제되는 부분을 수정해야 합니다.
  9. 여기서 빌드에 관련한 자세한 내용을 살펴보려면 Console Output 메뉴를 클릭해야 합니다.

    • 콘솔 출력을 확인하면 작업의 진척 상황이나 작업의 실패 원인을 파악할 수 있습니다. 다음 그림은 작업이 성공적으로 진행돼 문제가 없는 내용입니다.
  10. Terminus 창으로 돌아가 쿠버네티스 클러스터에 디플로어먼트와 로드밸런서가 정상적으로 배포됐는지 확인합니다.

    • kubectl get deployments
    • kubectl get services
    • 디플로이먼트 fs-echo-ip와 로드밸런서 fs-echo-ip-svc가 정상적으로 배포된 것을 확인할 수 있습니다.
  11. 좀 더 확실하게 호스트 웹 브라우저를 열어 192.168.1.12:8080에 접속해 배포된 파드의 정보가 화면에 출력되는지 확인합니다.

    • 기존 로드밸런서(jenkins)는 80번 포트로 연결되어 있기 때문에 뒤에 포트(:80)을 입력해주지 않아도 192.168.1.11만 입력하면 접속이 안됐습니다. 하지만 fs-echo-ip-svc는 포트(:8080)을 붙여줘야 접속됩니다.
    • 배포가 정상적으로 됐다면 이 부분에 문제가 없으므로 echo-ip 애플리케이션 배포에 대해서는 따로 웹을 통해 확인하지 않겠습니다.
  12. 다음 실습을 위해 젠킨스로 배포한 로드밸런서 서비스와 디플로이먼트를 삭제하겠습니다.

    • kubectl delete service fs-echo-ip-svc
    • kubectl delete deployment fs-echo-ip

왜 배포된 오브젝트를 젠킨스로 삭제하지 않나요?
젠킨스는 CI/CD를 위한 도구입니다. 만약 젠킨스를 통해 배포한 애플리케이션을 삭제한다면 삭제 과정을 추가 프로젝트로 생성해야 합니다!
따라서 이 포스팅에서는 간단하게 삭제할 수 있는 kubectl을 이용해 직접 삭제합니다.
삭제된 오브젝트는 다시 Build Now를 눌러 언제라도 다시 배포할 수 있습니다.

  1. 그리고 젠킨스 홈 화면에서 dpy-fs-dir-prod 프로젝트도 삭제합니다.
    • 대시보드로 돌아갑니다.
    • 프로젝트의 Name을 클릭하면 작업 목록이 뜨고, 여기서 Project 삭제를 클릭하면 됩니다.

Freestyle 프로젝트는 젠킨스의 웹 화면에 직접 셸 스크립트를 입력하기 때문에 빌드 작업의 명렁어에 변경이 있을 경우 작업 관리 및 변경 사항의 추적이 쉽지 않습니다.
그래서 이러한 내용을 파일 레벨로 관리하고 이에 대한 변경 관리를 도와주는 깃허브 레포지터리를 함께 사용한다면 어떨까요?
이와 같이 구성한다면 이력 관리 및 변경 추적, 그리고 애플리케이션 통합이 훨씬 수월하겠죠?
이렇게 파일 레벨로 CI/CD를 구성하게 도와주는 아이템이 Pipeline입니다.

Pipeline 프로젝트로 손쉽게 echo-ip 배포하기

젠킨스의 Pipeline은 연속적인 작업을 코드 또는 파일로 정의해주는 젠킨스 기능입니다.
Pipeline은 고유의 문법으로 작성된 코드 또는 이러한 내용을 담고 있는 파일로 이루어져 있습니다.
파이프라인 문법을 통해 젠킨스는 코드로 작성한 내용이 실제 공작하는 작업이 되는 코드로서의 파이프라인(Pipeline-As-Code)을 구현할 수 있습니다!

Freestyle은 웹 화면에서 메뉴를 눌러서 필요한 것을 정의하는 방식이기 때문에 간단한 단일 작업을 정의할 때는 유용합니다.
그러나 CI/CD는 빌드-테스트-패키징-배포 등의 여러 단계로 나누어진 작업들이 효과적으로 이루어져야 합니다.
그러나 프리스타일의 경우 화면에서 메뉴를 눌러 정의하는 방식이기 때문에 여러 사람들에게 전달하려면 사용법을 pdf 등의 책자로 만들고 교육해야 합니다.
또한 일부 내용이 변경되면 해당 책자를 다시 만들거나 변경된 내용을 교육해야 합니다. 관리가 부실하면 버전을 착각하기도 쉽겠죠?
그래서 젠킨스에서는 Pipeline을 통해서 CI/CD 내용을 코드 또는 파일로 정의해 단순히 해당 코드 또는 파일을 가져다 쓰면 모든 것이 쉽게 진행되도록 지원합니다!

편하게 Pipeline을 파이프라인으로 부르겠습니다.
젠킨스 파이프라인은 크게 두 가지의 문법으로 코드를 작성할 수 있습니다.
첫 번째는 스크립트(Scripted pipeline) 문법이고 두 번째는 선언적인(Declarative pipeline) 문법입니다.
각 문법은 일반적인 경우에는 큰 차이점이 없으나, 쿠버네티스상에서 젠킨스 에이전트를 설정할 때 스크립트 문법을 사용하면 익숙하지 않은 젠킨스 고유 문법으로 작성해야 합니다.
하지만 선언적인 문법을 사용하면 우리가 이미 익숙한 야믈을 그대로 사용할 수 있으므로,
쿠버네티스상의 젠킨스 에이전트 설정에는 선언전인 문법을 사용하는 것을 권장합니다!
따라서 파이프라인 실습에서는 선언적인 문법을 위주로 진행할 것이며, 일부 조건문에서는 표현이 다양한 스크립트 문법으로 작성하겠습니다.

간단한 배포의 경우에는 에이전트를 설정할 필요가 없기 때문에 큰 차이가 있지 않습니다.
하지만 이미 설명했던 것처럼 젠킨스 에이전트를 설정하는 경우에는 큰 차이가 발생하는데, 이는 이후 실습에서 알아보겠습니다.

실습을 진행하기 전에 파이프라인으로 어떻게 CI/CD를 구현하는지 간략히 정리해봅시다.

  1. 깃허브와 같은 소스 코드 저장소에서 Jenkinsfile을 내려받습니다.

    • 빌드할 소스 코드와 젠킨스 내부의 작업을 선언적인 문법으로 정의해 둔 파일입니다.
  2. 내려받은 Jenkinsfile을 해석해서 작성자의 의도에 맞는 작업을 자동으로 수행합니다.

    • 이때 Jenkinsfile을 통해 이루어지는 것은 앞서 진행한 프리스타일로 진행한 내용과 같습니다. 이를 간편하게 사용하기 위해서 Jenkinsfile로 정의해 둔 것입니다.

그러면 파이프라인으로 배포하는 과정을 실습해봅시다! 앞서 프리스타일로 배포했던 echo-ip를 얼마나 간단하게 배포할 수 있는지 확인해보시죠.

  1. Pipeline 배포 실습을 위해 젠킨스 홈 화면으로 이동한 후에 새로운 Item 버튼을 눌러서 새로운 아이템을 생성하는 화면으로 넘어갑니다.

  2. 이번엔 Pipeline을 선택하며, 이름은 dpy-pl-bulk-prod입니다. 모든 선택을 마쳤다면 OK 버튼을 눌러 세부 설정을 하는 메뉴로 넘어갑니다.

  3. General 탭은 프로젝트의 일반적인 설정을 기록하는 곳입니다.

    • 프로젝트의 설명 및 빌드 작업의 동작 방식에 대한 여러 설정이 있지만, 이와 관련된 설정을 하지 않아도 실습을 진행하는 데 문제가 없으므로 기본 설정으로 사용하겠습니다.
  4. Build Triggers 탭은 빌드를 유발하기 위한 조건을 설정하는 탭입니다. 사용자의 환경에 따라 빌드에 필요한 조건이 달라질 수 있기 때문에 존재하는 설정입니다. 탭에서 제공하는 옵션을 살펴보겠습니다.

    • Build after other projects are built
      - 다른 프로젝트를 빌드한 이후에 이 프로젝트를 빌드합니다.
      - 특정 프로젝트를 빌드하기 위한 사전 조건을 구성해야 하는 경우 혹은 여러 개의 프로젝트를 빌드할 때 순서에 따른 의존 관계가 있는 경우에 유용합니다.
    • Build periodically
      - 주기적으로 프로젝트 빌드를 수행합니다.
      - 일정 주기로 빌드를 수행하는 경우에 사용할 수 잇습니다.
      - 예를 들어 매일 최신 버전의 소프트웨어를 배포하는 방식을 야간 빌드(Nightly build)라고 하는데 이러한 작업을 위해 1일 주기로 작업을 설정할 수 있습니다.
      - 주기를 설정할 때는 크론(Cron)이라는 스케줄 도구의 문법을 활용해 작성합니다.
    • Poll SCM
      - 깃허브 등의 소스 코드 저장소에서 주기적으로 내용을 검사해 빌드합니다.
      - Poll SCM 또한 크론 문법을 사용해 주기적으로 빌드를 수행합니다.
      - Build periodically와 차이점은 Poll SCM은 빌드를 수행하기 전 소스 코드 저장소의 내용에 변경이 있는지 확인합니다. 이후 변경이 있을 때만 빌드를 수행합니다.
    • 빌드 안함
      - 빌드를 사용하지 않습니다.
      - 임시로 사용하지 않을 프로젝트 등에 설정할 수 있습니다.
      - 이 옵션을 사용하면 웹 화면상에 '이 프로젝트는 현재 비활성 상태입니다'라는 안내가 표시됩니다.
    • Quiet period
      - 빌드를 실행할 때 약간의 지연 시간을 주는 옵션입니다.
      - 지연 시간의 범위 이내에서 들어온 요청은 한 건으로 처리하기 때문에 불필요한 작업의 중복을 방지할 수 있습니다.
      - 예를 들면 Quiet period를 5초로 설정한다면, 여러 번의 푸시가 들어오더라도 5초 내에 들어온 마지막 푸시만을 실행합니다.
      - 단, 젠킨스 UI의 Build Now는 즉시 작업을 수행하기 때문에 이 값을 생략합니다!
      - 깃허브와 같은 소스 코드 저장소와 연계해 외부의 요청으로부터 작업이 수행되는 경우에 적용할 수 있습니다.
    • 빌드를 원격으로 유발
      - 외부와 연계를 위해 젠킨스의 빌드 작업을 외부에서 URL을 호출해 시작할 때 사용합니다.
      - 이 옵션을 선택하면 작업 실행 권한의 인증을 위한 토큰(Token)을 입력할 수 있습니다.
      - 토큰을 설정한 후 <JENKINS_URL>/job/<작업명>/build?token=<토큰 이름>의 형식으로 URL을 호출하면 빌드 작업이 사작됩니다.
      - 이 주소는 주로 깃허브의 푸시 또는 메신저의 웹훅(webhook)과 같이 주소를 이용해서 빌드를 시작할 수 있는 곳에서 사용됩니다.
  5. Advanced Project Options 탭은 프로젝트의 고급 옵션을 설정하는 곳으로 젠킨스 플러그인 설치에 따라 생성됩니다.

    • 이번 실습에서는 별도로 고급 옵션을 설정하는 플러그인이 설치되지 않은 환경입니다.
  6. Pipeline 탭에서는 젠킨스의 빌드 작업 절차를 정의할 수 있습니다.

    • 빌드 작업을 위한 스크립트를 직접 입력하거나 외부의 소스 코드 저장소에서 선언적인 문법으로 작성된 파일을 가지고 와서 빌드 작업을 수행할 수 있습니다.
    • Definition에서 Pipeline script를 선택할 경우 해당 화면에서 Freestyle과 같이 직접 입력한 내용을 사용합니다. 여기서 차이는 선언적인 문법을 사용해야 한다는 것입니다!
    • 만약 Definition을 Pipeline script from SCM으로 설정할 경우 외부 소스 코드 저장소에서 선언적인 문법으로 작성된 파일을 가지고 와서 실행하게 됩니다.
      - 편의를 위해 이미 작성된 파일로 CI/CD를 구현해 보겠습니다. Pipeline script from SCM을 선택해주세요!
  7. 외부 저장소에서 선언적인 문법으로 작성된 파일을 가지고 오기 위해서 다음과 같이 지정합니다.

    • SCM은 Git으로 설정하고 Repository URL을 https://github.com/iac-source/echo-ip로 설정합니다.
    • 그리고 파일을 가지고 올 브랜치를 설정하기 위해 Branch Specifier를 */main으로 설정합니다.
    • 젠킨스에서 처리할 작업 정보를 지닌 파일이 Script Path에 Jenkinsfile(기본값)으로 입력돼 있는 것을 확인합니다.
    • 참고로 SCM은 소스 코드 관리(Source Code Management)를 의미하며, SCM은 대중적으로 깃허브를 많이 사용합니다.

깃허브 주소 끝에 붙는 .git에 대한 의미
깃허브의 저장소 주소를 SCM에서 사용할 때는 끝부분에 .git이 붙어 있는 경우와 없는 경우가 있습니다.
이는 순수하게 편의 목적으로 사용자가 직접 입력 시에는 .git을 제외하고 설명했으며, 깃허브 저장소에서 제공하는 주소를 복사해서 사용할 때는 .git이 포함되어 복사됩니다.
사실 주소 값에는 차이가 있지만 결과적으로는 차이 없는 결과가 나옵니다.
여기서 .git의 의미는 일반적인 확장자가 아닌 깃 작업 내용을 저장하는 공간을 의미합니다.
깃허브 저장소 경로 맨 끝에 .git에 대한 존재와 관계 없이 경로로 전달되는 모든 요청은
깃허브에서 .git 경로로 다시 전달해주기 때문에 결과적으로는 단순히 저장소 경로만으로도 SCM에 등록하고 코드를 내려받을 수 있는 것입니다.

  1. 이제 저장 버튼을 눌러서 Pipeline 프로젝트가 저장된 것을 확인합니다.

  2. 저장한 Pipeline 프로젝트를 빌드하기 전에 SCM을 통해서 깃허브 저장소에서 가지고 오는 Jenkinsfile의 소스를 발펴보고, 코드를 이용해서 어떻게 Pipeline프로젝트가 동작하는지 확인해보겠습니다.

    • 깃허브 저장소에 있는 Jenkinsfile 코드
      pipeline {
        agent any
        stages {
          # 깃허브로부터 소스 코드를 내려받는 단계, 이때 git 작업을 사용합니다. git 작업에서 인자로 요구하는 git url은 깃허브 저장소 주소를 사용하고, brach는 main을 설정했습니다.
          stage('git scm update') {
            steps {
              git url: 'https://github.com/IaC-Source/echo-ip.git', branch: 'main'
            }
          }
          # 도커 명령을 이용해서 컨테이너 이미지를 빌드하고, 빌드한 이미지를 레지스트리에 저장하는 작업을 수행, 이때 sh 작업을 통해 docker 명령을 사용합니다.
          stage('docker build and push') {
            steps {
              sh '''
              docker build -t 192.168.1.10:8443/echo-ip .
              docker push 192.168.1.10:8443/echo-ip
              '''
            }
          }
          # kubectl 명령으로 전 단계에서 레지스트리에 저장한 이미지를 pl-bulk-prod 디플로이먼트로 배포하고, 배포한 디플로이먼트를 로드밸런서 타입으로 노출하는 작업을 수행, sh 작업을 통해 kubectl 명령을 사용합니다.
          stage('deploy kubernetes') {
            steps {
              sh '''
              kubectl create deployment pl-bulk-prod --image=192.168.1.10:8443/echo-ip
              kubectl expose deployment pl-bulk-prod --type=LoadBalancer --port=8080 \
                                                     --target-port=80 --name=pl-bulk-prod-svc
              '''
            }
          }
        }
      }

Jenkinsfile의 구성 요소

  • pipeline
    - 선언적인 문법이 시작하는 부분입니다.
    - 선언적인 문법으로 작성된 작업들은 pipeline {}의 사이에 작업 내용을 작성해야 합니다.
  • agent
    - 작업을 수행할 에이전트를 지정하고 필요한 설정을 합니다.
    - 지정된 에이전트 내부에서 젠킨스 빌드 작업이 실제로 수행되는데 다음과 같은 여러 가지 방식으로 지정할 수 있습니다.
    - 첫 번째는 사용 가능한 에이전트를 젠킨스가 임의로 지정하는 any
    - 두 번째는 특정 레이블과 일치하는 에이전트 노드를 지정하는 label
    - 세 번째는 에이전트 노드의 이미지를 도커로 지정하는 docker
    - 네 번째는 에이전트 노드를 쿠버네티스 파드로 지정하는 kubernetes 와 같은 것들이 있습니다.
    - 플러그인에 따라 지정할 에이전트는 무수히 많습니다.
    - agent any로 사용하면 현재 설정된 에이전트가 하나만 존재하기 때문에 설정된 에이전트를 통해서 빌드 작업을 수행하게 됩니다.
  • stages
    - stage들을 모아서 정의하고 이를 순서대로 진행하게 해 줍니다.
  • stage
    - step들을 정의하는 영역입니다.
    - stage는 괄호 안에 여러 개의 step들을 정의할 수 있는데, 이 step들 내부에서 실제로 동작하는 내용들이 정의됩니다.
    - 그리고 젠킨스에서 빌드가 진행될 떄 stage별로 진행 단계를 확인할 수 있습니다.
  • steps
    - stage 내부에서 실제 작업 내용을 작성하는 영역입니다.
    - stage 내부에 여러 step이 존재할 수 있습니다.
    - step 영역 내부에서 script, sh, git과 같은 작업(work)을 통해서 실제로 동작하게 됩니다.
  1. 이제 다시 젠킨스 화면으로 돌아가 Build Now를 클릭해 젠킨스의 빌드 및 배포 작업을 시작합시다! 작업이 시작되면 Build History에 #1이 추가되는 것을 확인할 수 있습니다.

    • 작업이 성공하면 우측에 각 스테이지(Stage)별 단계와 작업의 성공 여부 소요 시간과 같은 다양한 정보가 표시됩니다.(Stage View)
  2. 각 스테이지별로 배포가 정상적으로 완료된 것을 확인했으면 실제로 쿠버네티스 클러스에 디플로이먼트와 로드밸런서가 정상적으로 배포됐는지 확인합니다.

    • kubectl get deployments
    • kubectl get services
  3. 다음 실습을 위해 배포한 서비스와 디플로이먼트를 삭제합니다.

    • kubectl delete service pl-bulk-prod-svc
    • kubectl delete deployment pl-bulk-prod

파이프라인 아이템을 이용하면 매우 간단하게 CI/CD를 코드로 정의하고 사용할 수 있는 것을 확인했습니다.
단순히 코드를 불러서 사용하는 것이 아니라 전략적으로 배포하는 것을 파이프라인 아이템을 통해 구현할 수 있었습니다.
다음 실습에선 파이프라인을 이용해 애플리케이션을 중단할 필요 없이 변경 및 업데이트하는 전략에 대해 알아봅시다!

Pipeline 프로젝트로 구현하는 블루그린 배포 전략

쿠버네티스에서 애플리케이션을 배포하는 것은 어렵지 않습니다.
하지만 파드의 특성상 배포되는 애플리케이션의 변경이 있다면 언제나 삭제하고 다시 생성하는 과정을 거칩니다.
따라서 중요한 서비스가 동작하고 있는 경우 이렇게 중단되는 시간이 발생하는 것은 큰 부담일 수 있습니다.

따라서 이번에는 변경된 애플리케이션을 중단 없이 배포하는 방법인 블루그린 전략을 젠킨스상에서 구현하는 방법에 대해 알아보겠습니다.
먼저 앞서 배포한 echo-ip 애플리케이션이 젠킨스를 통해 배포되는 흐름은 다음과 같습니다.

사용자 -> 트래픽 전달(MetalLB) -> Deployment(ReplicaSet) <- 배포 <- Jenkins <- 소스 가져오기 <- Github <- 관리자(개발자)

사용자의 요청은 MetalLB 로드밸런서를 통해 트래픽이 디플로이먼트로 전달됩니다.
이러한 배포 환경에서 개발자가 새로운 기능을 개발해서 배포하면 사용자에게 어떤 영향을 끼칠까요?
정답은 '중단 없이 배포가 된다'입니다.
그 이유는 이전에 rollout 기능을 이용한 파드 업데이트 방식 덕분입니다!
이와 같이 업데이트 방법을 롤링 업데이트(Rolling-Updata)라고 하며,
이를 간단히 정리하면
파드를 레플리카셋 단위로 나누어 모든 레플리카셋에 속해 있는 파드가 업데이트 된 이후에 레플리카셋을 삭제합니다.
디플로이먼트 내에 Old ReplicaSet과 New ReplicaSet이 있는데, Old ReplicaSet에서 파드를 하나씩 업데이트하고(New ReplicaSet이 하나씩 생성되겠죠?), 모든 Old ReplicaSet의 파드가 지워진다음에 Old ReplicaSet를 삭제하는 겁니다.

이런 롤링 업데이트 배포 과정에서 내부의 파드 개수가 많으면 업데이트 과정이 길어져 다른 두 가지의 버전이 오랫동안 공존하는 경우가 있습니다.(Old와 New 버전이)
이런 상황을 방지하는 좋은 방법 중의 하나는 블루그린(Blue-green) 배포 전략을 사용하는 것입니다.

블루그린 배포 전략이란
간단히 말해서 '모든 파드가 업데이트된 이후에 트래픽을 전달하자'입니다.
2개의 디플로이먼트를 생성하고 기존에 배포된 디플로이먼트(블루)로 계속 트래픽을 전달하고 있다가,
새로 배포되는 디플로이먼트(그린)에 모든 파드가 업데이트돼 트래픽을 처리하는 데 문제가 없을 때 서비스를 모두 새로 배포된 디플로이먼트(그린)으로 넘깁니다.
그리고 기존에 디플로이먼트(블루)를 삭제합니다.
이와 같이 디플로이먼트에서 제공하는 서비스를 넘긴다면 서비스의 중단 없이 연속적으로 배포가 가능합니다.
그리고 문제가 발생한 경우 기존에 서비스하던 디플로이먼트(블루)로 원복하는 것도 수월해 장애 복구도 쉽습니다.
하지만 배포를 위한 디플로이먼트를 만들어야 하기 때문에 기존 디플로이먼트 배포 대비 최소 2배 이상의 리소스를 더 필요로 한다는 제약 사항이 있습니다.
하지만 장애 복구가 수월하다는 점과 무중단 배포가 가능하다는 장점이 더 크기 때문에 리소스 사용은 크게 부각되는 단점은 아닙니다.

쿠버네티스 환경에서 블루그린 배포는 기본 기능이 아니기 때문에 구성할 수 없지만,
젠킨스를 이용한다면 구현이 가능합니다!
이제 젠킨스를 이용해 쿠버네티스 환경에 맞는 블루그린 배포 전략을 어떻게 구현할 수 있는지 살펴봅시다.
블루그린 배포를 테스트하기 위해서 미리 구성한 대시보드 애플리케이션을 사용해 중단 없는 배포를 확인하겠습니다.

  1. 블루그린 배포를 구성하기 위해 홈 화면에 새로운 아이템을 눌러 진입합니다.

  2. 이번에 선택할 아이템은 Pipeline이며, 생설할 프로젝트 이름은 dpy-pl-blue-green입니다. 선택을 마쳤다면 OK 버튼을 눌러 세부 설정하는 메뉴로 이동합니다.

  3. 스크롤을 내려 Pipeline-Definition에서 외부 소스 코드 저장소에서 정의된 파일을 불러와서 사용하도록 Pipeline script for SCM을 선택합니다.

    • SCM을 git으로 하고 Repository URL은 https://github.com/iac-source/blue-green으로 설정한 다음 Branch Specifier를 */main으로 설정합니다.

    • 설정 완료 후 해당 내용을 저장 버튼을 눌러 저장합니다.

    • 프로젝트를 빌드하기 전에 블루그린 배포를 위해서 작성한 jenkinsfile이 달라진 부분이 있습니다.

      • agent 부분을 any가 아닌 kubernetes로 변경해 블루그린 배포를 위한 에이전트를 별도로 설저앟고, 이를 yaml 파일로 작성해 적용합니다.
      • 아까와 달리 agent any -> agent { kubernetes {} } 형식으로 변합니다.
      • 이렇게 작성하는 이유는 블루그린 배포에 동적으로 변동되는 오브젝트 값을 설정하려면 kustomize와 같은 도구를 사용해야 하기 때문입니다.
      • 그래서 kustomize를 사용하기 위해 전용 파드를 배포했습니다.
      pipeline {
        agent {
          # 쿠버네티스의 파드를 젠킨스 작업이 수행되는 에이전트로 사용합니다. kubernetes {} 내부에서는 에이전트로 사용할 파드에 대한 명세를 야믈 형태로 정의할 수 있습니다.
          kubernetes {
            # 야믈 '''~''' : 젠킨스의 에이전트로 만들어지는 파드의 명세입니다. kubectl 명령어로 파드를 생성하기 위해서 사용하는 매니페스트와 동일한 형식의 야믈을 사용할 수 있습니다. 이 야믈은 블루그린 배포에 필요한 kustomize가 설치된 컨테이너(sysnet4admin/kustomize:3.6.1)를 에이전트 파드에 포함하고 있으며 호스트에 설치된 kubectl 명령어를 사용하기 위해 호스트와 연결된 볼륨, 에이전트 파드가 쿠버네티스 클러스터에 오브젝트를 배포하기 위해 사용할 서비스 어카운트인 jenkins가 미리 설정돼 있습니다. 
            yaml '''
            apiVersion: v1
            kind: Pod
            metadata:
              labels:
                app: blue-green-deploy
              name: blue-green-deploy
            spec:
              containers:
              - name: kustomize
                image: sysnet4admin/kustomize:3.6.1
                tty: true
                volumeMounts:
                - mountPath: /bin/kubectl
                  name: kubectl
                command:
                - cat
              serviceAccount: jenkins
              volumes:
              - name: kubectl
                hostPath:
                  path: /bin/kubectl
            '''
          }
        }
        # 깃허브로부터 대시보드 소스 코드를 내려받는 단계입니다. 이때 소스코드를 내려받기 위해 git작업을 사용합니다. url과 branch를 확인해주세요.
        stages {
          stage('git scm update'){
            steps {
              git url: 'https://github.com/IaC-Source/blue-green.git', branch: 'main'
            }
          }
          # 실습에서는 젠킨스의 빌드 횟수마다 부여되는 번호에 따라 블루와 그린이 전환되는 것을 구현하기 위해서 젠킨스 스크립트(script)를 사용합니다. 젠킨스 빌드 번호(BUILD_NUMBER)가 홀수일 때 tag 환경변수값을 blue로 설정하고, 짝수일 때는 green으로 설정합니다. 이 단계에서 설정한 환경변수는 이후 작업에서 사용할 수 있습니다.
          stage('define tag'){
            steps {
              script {
                if(env.BUILD_NUMBER.toInteger() % 2 == 1){
                  env.tag = "blue"
                } else {
                  env.tag = "green"
                }
              }
            }
          }
          # 대시보드를 배포하기 위해 필요한 ConfigMap을 배포한 다음 디플로이먼트를 배포하는 단계입니다. 이 단계에선 배포 작업에 필요한 야믈 파일이 깃허브 저장소 하위 디플로이먼트 디렉터리에 위치해 있기 때문에 dir('deployment') 작업으로 디플로이먼트 디렉터리로 이동해서 작업을 수행하도록 지정합니다. 또한 디플로이먼트의 이미지, 이름, 레이블에 설정한 tag 환경 변수를 덧붙이는 것을 일일이 수정하지 않기 위해 kustomize 명령을 사용합니다. 이 kustomize 명령을 사용하기 위해서 container('kustomize') 작업으로 컨테이너 내부에서 sh 작업을 수행하도록 작성합니다.
          stage('deploy configmap and deployment'){
            steps {
              container('kustomize'){
                dir('deployment'){
                  sh '''
                  kubectl apply -f configmap.yaml
                  kustomize create --resources ./deployment.yaml
                  echo "deploy new deployment"
                  kustomize edit add label deploy:$tag -f
                  kustomize edit set namesuffix -- -$tag
                  kustomize edit set image sysnet4admin/dashboard:$tag
                  kustomize build . | kubectl apply -f -
                  echo "retrieve new deployment"
                  kubectl get deployments -o wide
                  '''
                }
              }
            }    
          }
          # 블루그린 배포 전략을 위한 디플로이먼트 배포가 끝난 후 쿠버네티스 클러스터 외부에서 들어온 요청을, 로드밸런서에서 보내줄 대상을 다시 설정하는 단계입니다. 이 단계에선 로드밸런서 설정에 필요한 야믈 파일이 깃허브 저장소 하위 service 디텍터리에 위치해 있어 dir('service')로 작업 디렉터리를 이동해 수행하도록 지정합니다. 또한 service의 셀렉터(selector) 값들을 앞서 설정한 tag 환경변수를 덧붙이는 작업도 명령으로 처리하기 위해 위와 같이 kustomize를 설정하고 사용합니다. 전 단계에서 배포한 디플로이먼트의 replicas 값과 readyReplicas의 값을 비교해 값이 같은 경우 배포가 완료됐다고 판단합니다. 그리고 로드밸런서가 트래픽을 전송하는 대상을 배포 완료된 디플로이먼트로 설정한 다음 배포 이전에 존재하는 디플로이먼트를 삭제해 배포 완료된 디플로이먼트로 트래픽을 보내줍니다.
          stage('switching LB'){
            steps {
              container('kustomize'){
                dir('service'){
                  sh '''
                  kustomize create --resources ./lb.yaml
                  while true;
                  do
                    export replicas=$(kubectl get deployments \
                    --selector=app=dashboard,deploy=$tag \
                    -o jsonpath --template="{.items[0].status.replicas}")
                    export ready=$(kubectl get deployments \
                    --selector=app=dashboard,deploy=$tag \
                    -o jsonpath --template="{.items[0].status.readyReplicas}")
                    echo "total replicas: $replicas, ready replicas: $ready"
                    if [ "$ready" -eq "$replicas" ]; then
                      echo "tag change and build deployment file by kustomize" 
                      kustomize edit add label deploy:$tag -f
                      kustomize build . | kubectl apply -f -
                      echo "delete $tag deployment"
                      kubectl delete deployment --selector=app=dashboard,deploy!=$tag
                      kubectl get deployments -o wide
                      break
                    else
                      sleep 1
                    fi
                  done
                  '''
                }
              }
            }
          }
        }
      }

블루그린 배포 전략을 위한 깃허브 저장소의 디렉터리 구조는 다음과 같습니다.

  1. 이제 실제로 블루그린 배포 전략을 확인하기 위해 Pipeline 프로젝트 상세 화면에서 Build Now 버튼을 눌러 1차로 블루그랜 대시보드를 배포하고 배포가 완료된 것을 확인합니다.

    • 참고로 1차로 배포되는 대시보드는 파란색입니다.
  2. 이제 Terminus로 돌아가 대시포드 디플로이먼트와 서비스가 정상적으로 배포 및 할당됐는지 확인합니다.

    • 이때 여러 조건에 만족하는 오브젝트를 검색하기 위해 --selector 옵션을 사용하겠습니다.
    • kubectl get deployments,service --selector=app=dashboard

레이블은 무엇이고 셀렉터는 어떻게 사용하나요?
레이블은 쿠버네티스 오브젝트를 검색할 때 사용하는 메타데이터 중 하나입니다.
우리는 사실 이런 레이블을 모르는 사이에 많이 써오고 있었습니다.
서비스가 트래픽을 넘길 때 selector 아래 키와 값의 형태로 대상을 지정하는 등의 형식으로 사용되고 있었습니다.(아래 사진을 참조해주세요.)
이러한 레이블은 kubectl get <오브젝트> --show-labels 명령으로 오브젝트의 레이블을 확인할 수 있습니다.
kubectl get deployment --selector=deploy=blue --show-labels

  1. 호스트 브라우저에 192.168.1.12를 입력해 배포된 파란색 대시보드를 확인합니다.

  2. 블루그린 배포는 모든 배포가 완료되는 순간 새롭게 배포힌 대시보드로 전환됩니다.

    • 이를 확인하기 위해서 Terminus에서 kubectl get deployments --selector=app=dashboard -w를 우선 실행해 놓습니다.
    • 젠킨스 화면으로 이동해 Build Now 버튼을 눌러 두 번쨰 배포를 진행합니다.
    • kubectl get deployments --selector=app=dashboard -w

  3. 배포 이후 완료되기 전까지 웹 브라우저에서 새로고침을 해도 여전히 파란색 대시보드가 화면에 나타나는 것을 확인할 수 잇습니다.

    • 다시 Terminus를 확인해 배포가 완료된 시점에 pl-blue 디플로이먼트가 삭제되는 것을 확인합니다.
    • 첫 pl-blue는 존재했던 디플로이먼트이고, 이후 pl-green 3개는 ContainerCreating 상태, 그 이후 gl-green 1개는 Pending 상태, 마지막 pl-green 3개는 Running(1,2,3) 상태이며, 맨 마지막에 나오는 pl-blue는 새 배포 후에 삭제되는 것입니다. (옵션이 -w이기 때문에 진행상황을 모두 볼 수 있음)
  4. 모두 배포가 완료됐다면 다시 Terminus 창으로 돌아가 대시보드 디플로이먼트와 서비스가 정상적으로 배포 및 할당됐는지 확인합니다.

    • kubectl get deployments,service --selector=app=dashboard
    • gl-blue 디플로이먼트가 사라진(삭제된) 것을 확인할 수 있습니다.
  5. 녹색 대시보드를 위한 2차 배포가 완료되고 기존의 디플로이먼트인 pl-blue가 삭제된 것이 확인됐다면 파란색 대시보드가 보이는 브라우저에서 새로고침을 눌러 변경된 녹색 대시보드를 확인합니다.

  6. 다음 실습에서 혼동을 방지하기 위해 젠킨스 홈 화면으로 이동한 후에 Pipeline으로 생성한 모든 프로젝트를 삭제하겠습니다.

  7. 현재까지 생성된 것들로 인해 다음 실습이 영향을 받을 수 있습니다.

    • 그리고 호스트 자원도 충분히 확보해야 하므로 실습에서 사용했던 모든 오브젝트를 삭제하겠습니다.
    • kubectl delete deployments,service,configmap --selector=app=dashboard

이번 실습에서는 젠킨스의 아이템을 이용해 여러 종류의 프로젝트를 구성해 실제로 쿠버네티스 상에서 CI/CD가 동작하는 방식을 알아봤습니다.
젠킨스를 통한 CI/CD 아이템은 대표적으로 프리스타일과 파이프라인으로 나울 수 있으며, 상황에 맞게 이를 사용할 수 있습니다.
그리고 CI/CD 개념을 사용해서 서비스의 무장단 배포가 가능한 블루그린 배포 전략 또한 손쉽게 구현할 수 있었습니다.
기초적인 내용은 알아봤으니 젠킨스에서 제공하는 다양한 기능들을 조합해 단일 기능으로는 구현하기 힘든 GitOps를 실습해보도록 합시다!


젠킨스 플러그인을 통해 구현되는 GitOps

지금까지 우리는 젠킨스를 이용해 CI/CD를 구현하는 방법을 알아봤습니다.
거의 모든 기능은 사실 젠킨스의 플러그인(Plugin)을 통해 구현된 것입니다.
예를 들면 가장 많이 쓰였던 쿠버네티스 플러그인은 CI/CD를 실제로 수행하는
젠킨스 에이전트 파드를 사용자가 신경쓰지 않아도 자동으로 배포 관리하게 해줍니다.

현업에서는 젠킨스의 단일 플러그인으로 CI/CD를 구현하는 것이 아니라
여러 플러그인을 조합해 현재 업무에 맞는 형태로 만들어서 사용합니다.
레고 조립 방식과 비슷합니다!
사용자가 필요한 기능을 주로 젠킨스 플러그인 홈페이지에서 검색해 내용을 살펴보고 이를 조합하는 방식을 취합니다.
젠킨스 플러그인 홈페이지

젠킨스에서 제공되는 플러그인은 다음과 같은 종류로 구분됩니다.

  • Platforms
    - 웹 애플리케이션이 아닌 다른 플랫폼에서 작동하는 애플리케이션 빌드를 위한 플러그인 종류
  • User interface
    - 젠킨스의 기본 UI 이외의 확장 UI를 적용하기 위한 플러그인 카테고리
  • Administration
    - LDAP, 젠킨스 클러스터 관리 등 젠킨스 자체 관리에 필요한 플러그인 종류
    • LDAP는 Lightweight Directory Access Protocol의 약자로, 분산 디렉터리 서비스에서 사용자, 시스템, 네트워크, 서비스, 앱 등의 정보를 공유하기 위한 오픈 프로토콜입니다.
  • Source code management
    - 깃허브, 깃랩과 같은 소스 코드 저장소의 연결이나 관리를 위한 플러그인 카테고리
  • Build management
    - CI/CD 단계에서 추가적으로 사용할 수 있는 플러그인 종류

이러한 플러그인들을 조합하면 단일 플러그인으로 만들지 못하는 기능을 생성할 수 있습니다.
따라서 이번에는 쿠버네티스용 지속적 배포(Kubernetes Continuous Deploy), 슬랙 알림(Slack Notification), 변경 내용 비교(Last Changes) 총 3개의 플러그인을 조합해 젠킨스에서 GitOps를 구현해보겠습니다.

그런데 GitOps란 무엇일까요?
GitOps는 Git과 Ops(Operations, 운영)의 합성어로 깃을 통해 모든 것을 선언적으로,
깃허브 저장소와 같은 SCM에 업데이트하면 오퍼레이터(젠킨스와 같은 도구)가 변경분을 감지해 대상 시스템에 배포합니다.

이를 현재 쿠버네티스 환경에 맞춰 설명하면
배포돼야 할 매니페스트 파일을 SCM에 저장하고 매니페스트가 새로 업데이트된다면
젠킨스가 이를 파악해 쿠버네티스 클러스터에 배포하는 모든 단계를 GitOps라고 합니다.
즉, 변경 내용을 SCM에 선언해두면 이를 젠킨스가 읽어서 운영 시스템에 적용하는 것입니다.
GitOps를 이용하면 다음과 같은 이점이 있습니다.

  • 깃허브 저장소의 내용과 실제 상용 및 운영 환경의 내용을 동일하게 가져갈 수 있습니다. 이를 통해 깃허브 저장소로 모든 내용을 단일화해 관리하고 히스토리도 관리할 수 있으며, 문제가 생기면 빠르게 복원할 수 있습니다.
  • 배포를 표준화해 자동으로 배포되도록 할 수 있습니다. 배포 과정을 미리 정의해 깃허브 저장소에 변경된 내용을 선언만 하면 모든 배포가 자동으로 진행됩니다.
  • 사람의 실수를 줄일 수 있습니다. 배포 과정이 자동화되므로 사람에 의해 발생하는 실수를 방지하고 견고한 시스템을 만들 수 있습니다.

쿠버네티스 환경에 적합한 선언적인 배포 환경

지금까지 젠킨스를 통한 쿠버네티스 배포에서는 cluster-admin 역할을 가지고 있는 jenkins 서비스 어카운트를 사용해서 쿠버네티스 오브젝트를 배포했습니다.
이렇게 설정된 jenkins 서비스 어카운트를 통해 현재 쿠버네티스 클러스트에 모든 오브젝트를 배포하는 것은 가능하나,
외부에 있는 쿠버네티스 클러스터에는 가지고 있는 권한이 없기 때문에 배포가 진행되지 않습니다.
(즉, 젠킨스 내부의 쿠버네티스 클러스터는 문제 없지만 젠킨스 외부의 쿠너베티스 클러스터엔 접근할 수 없습니다.)

따라서 외부 클러스터에 접근하려면 쿠버설정(kubeconfig) 파일을 이용해 외부 클러스터의 API 서버로 접근한 다음 오브젝트를 배포해야 하는데,
젠킨스 에이전트 파드에서 쿠버설정 파일을 내려받아 바로 사용하는 것은 보안적으로 문제가 있습니다.
따라서 쿠버 설정 파일을 젠킨스 컨트롤러에서 관리하고 상황에 따라 필요한 권한만을 제공하는 기능이 필요한데
이를 쿠버네티스용 지속적 배포 플러그인을 사용해서 구현할 수 있습니다.

GitOps를 사용한다는 것은 단일 클러스터에서도 유용하지만 기본적으로 여러 개의 목적을 가지는 다수의 클러스터 환경을 사용하는 경우가 많으므로,
효과적인 GitOps 구현을 위한 첫 번쨰 단계로 쿠버네티스용 지속적 배포 플러그인을 설치해
어떻게 클러스터의 오브젝트 관리 권한을 가지고 오는지 확인해 보겠습니다.
그리고 GitOps의 중요한 기능 중에 하나인 변화 감지는 젠킨스의 기본 플러그인인 Poll SCM을 통해 구현하겠습니다.
Poll SCM은 변화를 감지하고 변화시에 변경된 부분을 쿠버네티스에 적용합니다.
이렇게 추가된 플러그인을 이용하면 외부 클러스터에도 배포 자동화를 구성할 수 있습니다.

현재의 테스트 환경은 다수의 크러스터 환경이 아니기 떄문에 단일 클러스터 환경에서 쿠버설정 파일을 읽어 들이는 실습을 진행해보겠습니다.

쿠버설정 파일이 담고 있는 내용에 대해서
쿠버설정 파일은 쿠버네티스 클러스터에 대한 접근 정보가 담겨 있는 파일입니다.
kubectl 명령어를 사용해서 쿠버네티스의 정보를 조회할 때 사용자 홈 디렉터리 하위의 .kube 디렉터리에 있는 config 파일을 기본적으로 이용합니다.
이 파일을 가지고 있으면 파일에 정의된 권한을 가지고 쿠버네티스 클러스터에 접근할 수 있습니다.
쿠버설정 파일은 쿠버 API 서버 접속 정보 이외에 연결에 필요한 모든 인증 정보를 가지고 있습니다.
쿠버설정 파일은 kubectl config view 명령을 통해 확인할 수 있습니다.

위의 정보 중에서 보안을 위해 민감한 정보는 생략(DATA+OMITTED, REDACTED)돼 있으며,
필요시 충분한 권한을 가진 사용자는 --raw 옵션을 추가해 생략된 정보를 확인할 수 있습니다.
출력된 쿠버네티스 클러스터에 접근하기 위한 접속 정보에 대한 설명은 다음과 같습니다.

  • clusters
    - 어떤 쿠버네티스 클러스에 접근할지에 대한 정보
    • clusters는 접속 대상이 되는 클러스터의 정보를 여러 개 포함할 수 있음
    • 각 클러스터 접속 정보는 API 서버의 주소와 인증 기관 정보(certificate-authority-data)로 이루어짐
  • contexts
    - 위의 클러스터의 정보와 곧이어 설명할 사용자 정보의 조합이 담긴 부분
    • kubectl 명령어를 통해 클러스터에 접속할 때는 현재 context에 설정된 user의 자격을 가지고 설정된 cluster에 접속
  • current-context
    - 현재 사용중인 contxt가 무엇인지 나타내는 부분
  • users
    - 클러스터에 접속하는 사용자가 누구인지 사용자 정보가 담겨 있는 부분
    • 현재 kubernetes-admin이라는 이름을 가진 사용자 정보가 등록돼 있으며 사용자 정보는 클라이언트의 인증서 정보(client-certificate-data) 및 클라이언트의 키 정보(client-key-data)를 속성으로 가짐
    • 쿠버네티스 클러스터는 위에서 설명한 cluster 속성에 담겨 있는 인증 기관 정보를 통해 검증할 수 있는 클라이언트의 인증서와 키를 이용해서 접속하는 사용자를 허용

이렇게 쿠버설정 파일은 쿠버네티스 클러스터에 접근할 수 있는 매우 민감한 정보를 포함하고 잇기 때문에 취급에 유의해야 합니다.

  1. 주기적으로 변화를 감지해야 하는 깃허브 저장소는 모두 같은 저장소를 공유할 수 없기 떄문에 실습 사용자마다 필요합니다.

    • 따라서 깃허브에서 다음 그림과 같이 Create repository 버튼을 눌러 깃허브 저장소를 생성합니다.
    • README.md 파일 생성에 체크하지 마세요! 실습 진행 중에 conflict 오류가 납니다.
  2. 저장소 생성 이후에 나타난 화면에서 복사 아이콘을 눌러 깃허브 주소를 복사합니다. 이 주소는 이후 생성한 매니페스트를 푸시하기 위해 사용되는 주소입니다.

실습에서 주로 사용하는 git 명령어

  • 초기화(init) : 현재 디렉터리를 깃 작업할 수 있도록 선언해 줍니다.
  • 원격(remote) : 깃허브 저장소와 같은 원격 저장소를 지정합니다.
  • 추가(add) : 파일 또는 디렉터리를 깃을 통해 추적하도록 설정합니다.
  • 커밋(commit) : 깃을 통해 추적하는 파일의 변경 사항을 저장합니다.
  • 푸시(push) : 변경 사항이 기록된 로컬 깃의 파일들을 원격 저장소로 보냅니다.
  1. GitOps의 내용을 저장할 디렉터리(gitops)를 m-k8s 홈 디렉터리(~)에 생성합니다. 그리고 생성한 디렉터리로 이동합니다.

    • mkdir ~/gitops
    • cd ~/gitops
  2. gitops 디렉터리에서 git init 명령을 사용해 깃 관련 작업을 할 수 있도록 준비합니다.

    • 명령을 실행한 이후 깃 작업 내용을 저장하는 .git 디렉터리가 생성되는 것을 확인할 수 있습니다.
  3. 깃을 통해 원격 저장소에 파일들을 저장할 떄는 작업자 이름, 작업자 이메일 주소 등을 설정하는 게 좋습니다.

    • 또한 현재 환경에서 깃허브 저장소로 여러 번 푸시를 하게 되면 푸시할 때마다 깃허브 사용자 이름과 비밀번호를 요구하기 때문에 자격 증명 저장소(Credential Store) 를 이용해서 번거로운 상황이 발생하지 않도록 자격 증명 헬퍼(credential.helper) 를 설정해 자격 증명이 영구적으로 저장되도록 하겠습니다.
    • git config --global user.name "<사용자 이름>"
    • git config --global user.email "<사용자 이메일>"
    • git config --global credential.helper "store --file ~/.git-cred"
  4. 원격 저장소에 작업한 파일들을 깃허브 저장소에 업로드할 수 있도록 저장소의 주소를 추가합니다.

    • 여기서 origin은 사용자의 깃허브 저장소 주소에 대한 또 다른 이름(alias)입니다.
    • git remote add origin <사용자의 깃허브 저장소 주소>
  5. 젠킨스에서 선언적으로 쿠버네티스 오브젝트를 배포하기 위해서 사전에 구성해 둔 파일들을 홈 디렉터리 밑에 gitops 디렉터리로 복사합니다.

    • cp ~/_Book_k8sInfra/ch5/5.5.1/* ~/gitops/
  6. 사전에 구성해 놓은 Jenkinsfile에는 쿠버네티스 배포를 위한 설정이 이미 구현돼 있습니다.

    • 하지만 깃허브 저장소는 개별 사용자에 맞는 설정이 필요하므로 sed 명령으로 깃허브 저장소를 변경합니다.
    • 기존에 사용했던 sed는 s/변경 대상/변경할 값/g를 사용했지만 변경할 값에 /(슬래시)가 포함돼 있어 깃허브 저장소 주소로 변환되지 않습니다.
    • 그래서 /(슬래시)를 ,(쉼표)로 대체해 깃허브 저장소 주소가 정상적으로 변환되도록 합니다.
    • sed -i 's,Git-URL,https://github.com/moey920/GitOps.git,g' Jenkinsfile

  7. 이제 git이 파일들을 추적할 수 있도록 git add . 명령으로 파일등 등록하겠습니다.

    • .은 현재 디렉터리의 모든 내용을 의미합니다.
    • git add .
  8. 추가한 내용을 커밋하기 전에 앞서 우리가 설정한 값들이 제대로 설정됐는지 확인해 보겠습니다.

    • git config --list
    • credential.helper=store --file ~/.git-cred는 매우 중요한 정보로 추후에 절대로 노출되지 않도록 주의하세요!
  9. 추가한 파일들을 푸시하기 위해서 git commit 명령으로 변경 사항을 저장하겠습니다.

    • 커밋 작업에서 사용하는 -m(message) 옵션은 푸시하는 내용 또는 의미를 깃허브 저장소에 남겨 추후에 내용을 파악하기 위한 목적으로 사용됩니다.
    • init commit은 최초 커밋을 의미합니다.
    • 원래 레포지터리에 아무것도 없었는데, cp를 이용해 파일을 복사해왔기 때문에 3개의 변경사항이 생긴 것을 볼 수 있습니다.
  10. 깃허브 저장소로 푸시하기 위해서 업로드 되는 브랜치는 git branch 명령으로 설정해야 합니다.

    • 브랜치란 코드를 보관할 수 있는 단위로 상황에 따라 여러 브랜치를 구성해 작업 내용을 분리해 저장할 수 있습니다.
    • 옵션으로 사용하는 -M(Move)은 브랜치 이름을 바꾸는 옵션이고 main은 깃허브의 기본 브랜치 이름입니다.
    • git branch -M main
    • 처음에 git branch로 브랜치 목록을 검색하면 master가 있는 것이 보이죠? 이전엔 깃허브 저장소의 메인 브랜치의 default가 master 브랜치였습니다. 최근엔 메인 브랜치를 main으로 쓰는 것이 정석입니다.
  11. 브랜치를 설정했다면 이제 gitops 디렉터리에 있는 파일들을 git push 명령으로 깃허브 저장소에 푸시하겠습니다.

    • 이떄 사용하는 origin은 깃허브 저장소의 주소를 의미하며 이에 대한 옵션엔 -u(--set-upstream)은 로컬 브랜치에서 원격 저장소로 업로드 하는 것을 의미합니다. 그리고 업로드된 브랜치를 이후 git pull 작업에서도 원격 저장소로 사용합니다.
    • 명령 수행 후에 깃허브 저장소에 접근하기 위한 사용자 이름과 비밀번호(또는 토큰)을 입력하면 푸시 작업이 진행됩니다.
    • 이후 실습에서는 자격 증명 헬퍼를 통해서 로그인 정보가 저장(~/.git-cred)돼 있기 때문에 계정 정보를 입력할 필요가 없습니다.
    • 참고로 2021년 8월 13일부터 단순 비밀번호로는 깃허브에 코드를 푸시할 수 없으므로 토큰이나 SSH 접근 방식을 이용해야 합니다.(아래 그림에 지원하지 않는다고 나옵니다. 깃허브 docs에 토큰을 발급받는 방법이 안내되어 있으니 참고해서 토큰을 발급받으세요!)
    • 깃허브 개인 토큰 발행
    • 저는 non-fast-forward 이슈로 push가 rejected되는데, 먼저 git pull을 수행해서 문제를 해결하도록 하겠습니다.(제가 레포지터리를 만들 때 README.md 파일 생성하기를 체크해서 confilct 에러가 발생합니다. 충돌을 해결하고 merge한 뒤, 다시 push하겠습니다.)

github 토큰 발급받기
1. 깃허브 세팅에 들어갑니다.

2. 왼쪽 메뉴바에서 Developer setting을 클릭합니다.

3. Personal access tokens 탭에서 Generate New Token을 클릭합니다.

4. 로그인합니다.

5. 토큰 만료기한과 토큰 권한을 설정합니다. 저는 편의를 위해 올해 말까지, 모든 권한을 설정했지만 실무에서 사용할 때는 적당한 기한과 필요한 권한만 부여하도록 합시다.

6. 하단의 Generate Token을 클릭하면 토큰이 생성됩니다. 복사해서 어딘가에 저장해두고 사용하도록 합시다. 또한 토큰이 노출되지 않도록 주의해야하며, 활용할 땐 하드코딩하지 말고 환경변수로 사용하길 권장드립니다.(노출문제로 스크린샷은 생략합니다.)

  1. 깃허브 저장소로 첫 푸시가 완료된 후에, 깃허브 저장소에 파일들이 정상적으로 푸시됐는지 확인합니다.

  2. 쿠버설정 파일을 안전하게 관리하기 위해서 쿠버네티스용 지속적 배포 플러그인을 설치하겠습니다.

    • 이를 위해 젠킨스 홈 화면 > 젠킨스 관리 > 플러그인 관리 > 설치 가능 탭으로 이동합니다.
    • kubernetes를 검색 필드에 입력하면 Kubernetes Continuous Deploy가 나타납니다. 해당 플러그인을 체크하고 지금 다운로드하고 재시작 후 설치하기를 누릅니다.
  3. 설치가 끝나고 실행중인 작업이 없으면 Jenkins 재시작을 체크해 작업이 끝나면 젠킨스를 리로드합니다.

    • 이전에 설명한 것처럼 플러그인 설치 후에 젠킨스가 자동으로 시작되지 않은 경우가 있는데, 이런 경우 브라우저를 새로고침하기 바랍니다.
  4. 다시 젠킨스에 로그인해서 설치된 쿠버네티스용 지속적 배포 플러그인에 대한 설정을 진행하겠습니다.

    • 쿠버네티스용 지속적 배포 플러그인은 쿠버설정 파일을 관리할 수 있게 자격 증명 정보를 따로 관리합니다. 따라서 다수의 쿠버네티스 클러스터를 안전하게 관리할 수 있습니다.
    • 이제 쿠버네티스용 지속적 배포 플러그인에서 제공하는 쿠버설정 파일 설정 메뉴로 이동해봅시다.
    • 설정을 위해 젠킨스 홈 화면 > 젠킨스 관리 > Manage Credentials 메뉴로 이동합니다.
  5. 쿠버네티스용 지속적 배포 플러그인이 사용할 새로운 자격 증명 정보를 추가하기 위해 (global) 버튼을 누릅니다.

  6. 쿠버설정 파일에 대한 자격 증명을 가져오려면 현재 쿠버설정 파일이 있는 마스터노드에 접속 권한이 있어야 합니다.

    • Add Credentials을 눌러 마스터 노드에 대한 자격 증명을 설정합니다.
  7. 다음과 같이 설정하고 OK 버튼을 눌러서 설정한 자격 증명을 저장합니다.

    • Kind : 자격 증명 종류를 선택하는 메뉴입니다. 마스터 노드 접속 시 사용자 이름과 비밀번호는 입력받는 형식으로 사용하기 위해 Username with Password를 이용합니다.
    • Scope : 자격 증명이 적용되는 범위를 정합니다. 젠킨스 전역에서 사용할 수 있게 Global을 선택합니다.
    • Username : 시스템에 접속하기 위한 사용자 이름을 입력합니다. 마스터 노드에 접속하는 기본 계정인 root를 입력합니다.
    • Password : 시스템에 접속하기 위한 비밀번호를 입력합니다. 베이그런트로 만들어진 가상 머신들의 초기 비밀번호는 vagrant 입니다.
    • ID :자격 증명을 사용할 때 식별하기 위한 값입니다.
    • Description : 자격 증명에 대한 간단한 설명을 작성할 수 있습니다.
  8. 마스터 노드의 자격 증명이 등록된 것을 확인하고, Add Credentials를 눌러 쿠버설정 파일에 대한 자격 증명을 추가합니다.

  9. 쿠버네티스 접속 자격 증명 설정을 다음과 같이 입력하고 OK를 누릅니다.

    • 쿠버네티스용 지속적 배포 플러그인이 사용할 쿠버설정 파일 등록을 위해 kubernetes configuration을 선택합니다.
    • 마스터 노드에 접근하기 위한 자격 증명인 root/**** m-k8s ssh credential을 선택합니다.
  10. 쿠버네티스 접속 자격 증명이 kubeconfig 라는 이름으로 등록된 것을 확인합니다.

  11. 선언적인 배포 환경을 위한 젠킨스 세부 구성이 완료됐으므로 젠킨스 홈 화면으로 이동한 후에 새로운 Item을 눌러 본격적으로 선언적인 배포 환경을 위한 프로젝트를 설정하겠습니다.

  12. 선언적인 배포 환경을 위한 프로젝트 설정을 위해 Pipeline 아이템을 선택하고 dpy-pl-gitops를 이름으로 입력하고 OK버튼을 눌러 설정 단계로 넘어갑니다.

  13. 깃허브 저장소의 변경 내용을 감시하기 위해 Pipeline 프로젝트에서 지원하는 기능인 Poll SCM을 사용해 주기적으로 깃허브 저장소의 변경을 인식하게 합니다.

    • 젠킨스에서 주기적으로 변경을 감시하기 위해 스케쥴을 */10 * * * *로 입력합니다.
    • 이것을 크론 표현식(cron expression) 이라고 하며 현재 입력한 내용은 10분마다 변화가 있는지 체크하도록 설정하는 것입니다.
    • 즉 4시 1분에 시작했다면, 4시 10분에 변화를 체크하고, 4시 17분에 시작했다면 4시 20분에 변화를 체크합니다.
    • 사용자의 환경에 따라 변화 감지 주기를 너무 짧게 입력하는 경우 배포 도중에 변화를 다시 감지해 계속 배포가 진행되니, 변화 감지 주기는 적당한 주기로 설정해야 합니다.

젠킨스 크론 표현식에서 사용하는 H는 어떤 의미를 갖나요?
젠킨스에서 * * * * *와 같은 크론 표현식을 작성하면 H/* * * * *와 같은 크론 표현식으로 사용하는 것이 좋다는 메세지가 나타납니다.
젠킨스가 권장하는 H를 사용해 크론 표현식을 작성하면 기본적으로는 1분에 한 번씩 실행되지만, 부하가 없는 시점에 실행돼 정확한 시점을 보장할 수 없습니다.
따라서 현재 실습에서는 정확한 시점에 빌드되는 것을 확인하기 위해 일반적인 크론 표현식을 작성했습니다.

  1. Pipeline 프로젝트에서 사용할 소스 저장소를 구성합니다. 그리고 저장을 클릭합니다.

    • Definition은 Pipeline script from SCM으로 설정하고 SCM은 git으로 설정합니다.
    • 이후 나타나는 Repository URL은 사용자 깃허브 저장소 주소를 입력합니다.
    • Branch Specifier는 */main으로 설정해 메인 브랜치의 변경만 확인합니다.
  2. dpy-pl-gitops 프로젝트를 저장하고 나서 약 10분을 기다리면 Build History 항목에서 첫번째(#1) 배포가 진행되는 것을 확인할 수 있습니다.

    • 아까 설정한 크론 표현식으로 인해 딱 10분 단위로 배포가 진행됩니다.
  3. 배포 작업이 완료된 것을 확인합니다.(파란색 원)

  4. 깃허브 저장소에 푸시한 야믈 파일이 쿠버네티스 클러스터에 적용이 되어있는지 확인해보겠습니다.

    • 푸시한 야믈 파일에는 gitops-nginx 디플로이먼트가 배포되고 파드가 2개 준비되어 있습니다.
    • kubectl get deployments
  5. 선언적인 배포 환경을 테스트하기 위해 야믈 파일을 변경하고 깃허브 저장소에 푸시하면 쿠버네티스 클러스터에 이미 배포돼 있는 디플로이먼트도 변경되는지 확인해봅시다.

    • deployment.yaml을 sed 명령으로 편집해 replicas 수를 2개에서 5개로 변경합니다.
    • sed -i 's/replicas: 2/replicas: 5/' deployment.yaml
  6. 변경한 야믈 파일을 git에 추가하고 커밋한 후에 변경 내용을 저장소로 푸시하겠습니다. 이 작업들을 연결해 한 번에 실행하기 위해 ;(세미콜론)을 사용합니다.

    • git add . ; git commit -m "change replicas count" ; git push -u origin main
  7. 파일 변경 이후 푸시가 정상적으로 진행됐는지 저장소에서 확인해봅시다. 다음과 같이 커밋 메세지가 변경된 것을 확인할 수 있습니다.

  8. 푸시 이후 10분째가 되면 젠킨스가 깃허브 저장소의 변경 사항을 인지해 배포하고 다음 그림과 같이 배포가 완료된 것을 확인할 수 있습니다.

  9. #2 배포가 완료됐다면 현재 쿠버네티스 클러스터의 replicas 개수를 확인해 변경 사항이 배포됐는지 확인합니다.

gitops에서 가장 중요한 개념인 선언적인 배포 환경을 쿠버네티스용 지속적 배포 플러그인과 젠킨스 Poll SCM 플러그인을 사용해 구현해봤습니다.
GitOps는 선언적인 배포 이외에 관리하고 있는 환경에서 이루어지는 다양한 변화에 대한 알림이 필요합니다.
따라서 가장 안정적으로 동작하는 협업 플랫폼인 슬랙(Slack)을 이용해서 젠킨스 환경에서 일어나는 변화를 감지하고 알려주는 기능을 구현해 보겠습니다.

슬랙을 통해 변경 사항 알리기

보다 안정적인 GitOps 환경을 위해서는 구축한 시스템에 대한 알림 메세지 등을 받아 즉각 확인하고 대처할 필요가 있습니다.
이러한 환경을 구현하기 위해서 젠킨스는 협업 플랫폼과 기능을 연결할 수 있습니다.
많은 종류의 협업 플랫폼을 연결할 수 있으나, 이번에는 가장 대중적인 협업 도구인 슬랙을 사용해 알림 메세지를 받아보겠습니다.
슬랙에 대한 어느정도 이해가 있다고 가정하고 진행하겠습니다.

간단히 젠킨스와 슬랙을 연동하는 단계를 정리하면 다음과 같습니다.

  1. 젠킨스가 슬랙으로 메세지를 보낼 수 있는 대상인 슬랙 채널을 생성합니다.

  2. 슬랙 채널에서 젠킨스가 보내는 메세지를 전달할 수 있는 Jenkins CI 앱을 추가해 젠킨스에서 슬랙 채널 연동을 위한 토큰과 워크스페이스 도메인 주소 값을 확인합니다.

  3. 슬랙에서 발급한 토큰은 공개되면 매우 민감한 정보이므로 젠킨스 자격 증명에 토큰을 등록합니다.

  4. 젠킨스에서 슬랙으로 메세지를 보내기 위해서 슬랙 알림 플러그인을 설치하고 시스템 설정 메뉴에서 토큰과 워크스페이스 도메인 주소를 입력해 연동 작업을 마칩니다.

그러면 이제 젠킨스에서 슬랙으로 배포의 시작과 종료를 메세지로 보내는 기능을 설정해봅시다!

  1. 젠킨스에서 슬랙으로 메세지를 보내기 위해선 여러 가지 설정이 필요합니다.

    • 다음과 같이 본인의 슬랙 워크스페이스로 이동해 동작에 큰 문제가 없는지 확인합니다.
  2. 좌측 상단에 위치한 채널 추가 버튼을 누르면 채널을 관리할 수 있는 메뉴가 나타납니다. 나타만 메뉴에서 새 채널 생성을 누릅니다.

    • 저는 사내 슬랙을 사용하고 있기 때문에 불필요한 채널을 새로 생성하지 않고, 기존에 사용중인 채널을 활용해보도록 하겠습니다.
  3. 새 채널 생성 버튼을 누르면 채널 생성에 필요한 정보를 입력하는 대화상자가 나타납니다.

    • 다음과 같이 값을 입력하고 생성 버튼을 눌러 채널을 생성합니다.
  4. 생성 버튼을 누르면 다음과 같이 사용자를 추가할 것인지를 물어보는 대화상자가 나타납니다. 필요한 사용자를 추가하고 완료됨 버튼을 누릅니다.

  5. 알림을 수신 받는 채널을 구성했다면 상단의 워크스페이스 메뉴를 눌러 슬랙과 젠킨스를 연동할 앱을 설치하기 위해 설정 및 관리의 하위 메뉴인 앱 관리를 선택합니다.

  6. 슬랙 앱 관리 화면으로 이동했습니다. 이 화면에서는 필요한 앱을 검색해 슬랙에 추가 기능을 부여할 수 있고 삭제할 수도 있습니다.

  7. 앱 관리 화면에서 젠킨스와 슬랙 연동을 위해 Jenkins CI 이름을 검색하고 설치합니다.

  8. Jenkins CI를 슬랙에 추가합니다.

  9. Jenkins CI 앱에서 알림을 받을 채널을 아까 생성한 채널로 선택합니다. 또한 Jenkins CI 통합 앱 추가 버튼을 눌러 슬랙에 Jenkins CI 앱을 추가합니다.

  10. 새 통합 앱이 추가됐다는 메세지를 확인하고 Jenkins Ci와 슬랙을 통합하기 위한 연동 설정 지침이 표시된 것을 확인합니다.

  11. 연동 설정 지침 중에 필요한 정보가 있는 3단계 내용을 확인하기 위해 스크롤을 내려봅시다.

    • 3단계 내용은 젠킨스와 슬랙을 연동하기 위한 자격 증명 정보를 확인합니다.
    • 다음 그림에서 나타난 토큰을 비인가자가 알게되면 슬랙 채널로 메세지를 보낼 수 있습니다!
    • 이런 문제를 방지하고자 다음 그림에서 확인할 수 있는 통합 토큰 자격 증명 ID를 직접 프로젝트에 입력해서 구성하지 않고 젠킨스 자격 증명 관리를 통해 토큰을 이용하겠습니다.
  12. 통합 토큰 자격 증명 ID를 젠킨스 자격 증명 키로 등록하기 위해 젠킨스 홈 화면 > 젠킨스 관리 > Manage Credentials > (global) 을 눌러 자격 증명 화면으로 이동합니다.

  13. 슬랙으로 메세지를 보내기 위한 자격 증명 키를 등록합니다.

    • 키를 등록하기 위해 왼쪽에 있는 Add Credentials 버튼을 누릅니다.
  14. 다음과 같이 입력하고 OK 버튼을 누릅니다.

    • 아까 연동 지침 3단계의 통합 토큰 자격 증명 ID를 Secret 란에 넣으면 됩니다!
  15. 슬랙 자격 증명을 위한 암호키가 등록된 것을 확인합니다.

  16. 등록한 슬랙 자격 증명 암호키를 사용해서 젠킨스로부터 슬랙에 메세지를 보내려면 이를 도와주는 플로그인을 설치해야 합니다.

    • 따라서 젠킨스 홈 화면 > 젠킨스 관리 > 플러그인 관리 > 설치 가능 탭 으로 이동한 다음 검색 영역에서 slack을 입력해 검색합니다.
    • 검색된 플러그인 중 Slack Notification을 선택하고 지금 다운로드하고 재시작 후 설치하기 버튼을 누릅니다.
  17. 설치가 끝나고 실행 중인 작업이 없으면 젠킨스 재시작 체크박스를 체크해 리로드합니다, 잠시 후 새로고침을 눌러 재로그인을 진행합니다.

  18. 슬랙 알림 플러그인을 설치하게 되면, 젠킨스와 슬랙 연동 정보를 입력할 수 있습니다.

    • 연동 정보를 입력하기 위해서 젠킨스 홈 화면 > 젠킨스 관리 > 시스템 설정 으로 이동한 다음 스크롤을 가장 하단으로 내려 Slack 영역이 새로 생성된 것을 확인하고 다음과 같이 입력하고 저장합니다.
    • Workspace : 연동 지침 3단계에서 확인한 팀 하위 도메인을 입력합니다.
    • Default channel/member id : 젠킨스로부터 온 메세지를 받을 슬랙 채널을 입력합니다. 여기서는 앞서 설정한 jenkins-deploy-notice 혹은 각자 사용할 채널명을 입력합니다.
  1. 설정한 내용들을 통해 젠킨스로부터 슬랙으로 메세지가 정상적으로 발송되는지 확인해봅시다.

    • 이를 위해 사전에 구성한 Jenkinsfile을 복사해 기존의 Jenkinsfile을 덮어쓰겠습니다.
    • cp ~/_Book_k8sInfra/ch5/5.5.2/Jenkinsfile ~/gitops/
    • 슬랙 알림 플러그인을 추가하면서 변경된 Jenkinsfile은 다음과 같습니다.
      pipeline {
        agent any
        stages {
          stage('deploy start') {
            steps { //+추가됨: 배포 작업 이전에 배포 시작 알림 메세지를 슬랙 채널로 보냄
              slackSend(message: "Deploy ${env.BUILD_NUMBER} Started"
              , color: 'good', tokenCredentialId: 'slack-key')
            }
          }      
          stage('git pull') {
            steps {
              // Git-URL will replace by sed command before RUN
              git url: 'Git-URL', branch: 'main'
            }
          }
          stage('k8s deploy'){
            steps {
              kubernetesDeploy(kubeconfigId: 'kubeconfig',
                               configs: '*.yaml')
            }
          }
          stage('deploy end') {
            steps { //+추가됨: 배포 작업 이후에 배포 완료 알림 메세지를 슬랙 채널로 보냄
              slackSend(message: """${env.JOB_NAME} #${env.BUILD_NUMBER} End
              """, color: 'good', tokenCredentialId: 'slack-key')
            }
          }
        }
      }
  2. Jenkinsfile에 사용자의 깃허브 저장소를 입력하기 위해, 현재 변수(Git-URL)로 처리돼 잇는 부분을 sed 명령으로 변경합니다.

    • sed -i 's,Git-URL,<사용자의 깃허브 저장소 주소>,g' Jenkinsfile
  3. 필요한 내용을 변경한 Jenkinsfile을 git에 추가하고 커밋한 후에 푸시합니다.

    • git add . ; git commit -m "add slack notification" ; git push -u origin main
  4. 푸시가 정상적으로 수행됐는지 저장소를 확인해봅니다.

  5. 푸시한 후 10분째가 되면 젠킨스가 깃허브 저장소의 변경 사항을 인지해 배포 작업을 시작하고 완료한 것을 확인합니다.

  6. 프로젝트 #3 배포가 완료된 후에, 슬랙으로 이동해서 설정한 채널에 배포 시작과 종료에 관한 메세지가 왔는지 확인합니다.

젠킨스에서 발생하는 배포들에 대해서 슬랙을 연동해 메세지로 받으면 보다 빠르게 상태를 확인할 수 있고,
이에 따라 필요한 조치도 신속하게 취할 수 있습니다.
그런데 배포가 이루어지는 것을 확인은 했지만 어떤 내용이 변경돼 배포했는지 궁금하지 않으신가요?
다음으로 어떤 내용이 변경됐는지 확인하는 기능을 추가해 더욱 더 안정적인 GitOps 환경을 구축해보겠습니다!

배포 변경 사항을 자동 비교하기

변경된 부분을 직접 깃허브 저장소에서 확인할 수도 있지만, 이미 잘 만들어진 플러그인인 Last Changes를 활용해 변경된 내용을 슬랙을 통해 빠르게 확인해봅시다!
개발의 매력은 효율을 높히는 거니까요!

  1. 슬랙에서 코드 변경을 확인하기 위해 플러그인을 추가하겠습니다.

    • 젠킨스 홈 화면 > 젠킨스 관리 > 플러그인 관리 > 설치 가능 탭으로 이동해 last change를 입력해 플러그인을 검색후 설치합니다.
    • 지금 다운로드하고 재시작 후 설치하기 버튼을 누릅니다.
    • 설치가 끝나고 실행 중인 작업이 없으면 젠킨스 재시작을 체크해 작업 종료 후에 젠킨스를 다시 실행할 수 있게 합니다.
  2. 플러그인 설치 완료 후에 변경 내용 비교를 위한 구문이 추가된 Jenkinsfile을 가져와 현재 덮어쓰겠습니다.

    • cp ~/_Book_k8sInfra/ch5/5.5.3/Jenkinsfile ~/gitops/
    • Last Changes 플러그인을 사용하기 위한 Jenkinsfile 코드는 다음과 같습니다.
    • Last Changes 플러그인에서는 Pipeline 프로젝트에서 사용하는 선언적인 문법이 적용되지 않기 때문에 또 다른 문법인 그루비(Groovy) 스크립트를 사용해 script 이전 배포와 현재 배포의 차이를 찾아서 html 파일로 작성하도록 구성합니다. 이렇게 작성된 html은 슬랙 메세지로 전달되는 링크를 통해 확인거나 프로젝트 상세 화면의 좌측 메뉴를 통해 확인할 수 있습니다,
    • 주소를 슬랙 메세지로 전달하기 위해 앞서 구성한 slack-key 자격 증명을 사용합니다.
      pipeline {
        agent any
        stages {
          stage('Deploy start') {
            steps {
              slackSend(message: "Deploy ${env.BUILD_NUMBER} Started"
              , color: 'good', tokenCredentialId: 'slack-key')
            }
          }      
          stage('git pull') {
            steps {
              // Git-URL will replace by sed command before RUN
              git url: 'Git-URL', branch: 'main'
            }
          }
          stage('k8s deploy'){
            steps {
              kubernetesDeploy(kubeconfigId: 'kubeconfig',
                               configs: '*.yaml')
            }
          }
          stage('send diff') {
            steps {
              script { //+추가됨: 이전 배포와 현재 배포의 코드 변동 사항을 html로 만듦
                def publisher = LastChanges.getLastChangesPublisher "PREVIOUS_REVISION", "SIDE", "LINE", true, true, "", "", "", "", ""
                publisher.publishLastChanges()
                def htmlDiff = publisher.getHtmlDiff()
                writeFile file: "deploy-diff-${env.BUILD_NUMBER}.html", text: htmlDiff
              } //+추가됨: 변경 사항을 한눈에 확인할 수 있는 주소를 메세지로 전달
              slackSend(message: """${env.JOB_NAME} #${env.BUILD_NUMBER} End
              (<${env.BUILD_URL}/last-changes|Check Last changed>)"""
              , color: 'good', tokenCredentialId: 'slack-key')             
            }
          }
        }
      }
  3. Jenkinsfile에 사용자 깃허브 저장소를 입력하기 위해 변수로 처리돼있는 부분을 sed 명령으로 변경합니다.

    • sed -i 's,Git-URL,<사용자의 깃허브 저장소 주소>,g' Jenkinsfile
  4. 필요한 내용을 변경한 Jenkinsfile을 git에 추가하고 커밋한 후에 푸시합니다.

    • git add . ; git commit -m "add last changes" ; git push -u origin main
  5. 푸시가 정상적으로 수행됐는지 깃허브 저장소를 확인합니다.

  6. 푸시한 후 10분째가 되면 젠킨스가 깃허브 저장소의 변경 사항을 인지하고 배포 작업을 진행합니다. 완료된 것을 확인합니다.

  7. 배포 작업 완료 후 슬랙 채널에 배포의 시작과 종료에 관한 메세지가 도착한 것을 확인합니다.

    • 코드의 변경 내용을 확인하기 위해 마지막 배포 메세지에 생성된 Check Last changed 버튼을 누릅니다.
  8. 다음과 같이 코드에 대한 변경 내용을 확인할 수 있는 페이지로 이동시켜 줍니다.

    • 이 페이지는 변경 전 후 코드를 한눈에 알아볼 수 있는 화면을 제공합니다.
  9. 젠킨스 실습을 위해 지금까지 사용했던 오브젝트 중 디플로이먼트만 삭제하겠습니다. CI/CD를 다루는 젠킨스는 쿠버네티스 인프라를 이루는 주요한 요소이므로 삭제하지 않겠습니다.

    • 디플로이먼트를 삭제한 후에 홈 디렉터리로 이동합니다.
    • kubectl delete deployment gitops-nginx
    • cd ~

이버 포스팅에서는 쿠버네티스 환경에서 젠킨스를 손쉽게 설치할 수 있는 도구인 커스터마이즈와 헬름에 대해 알아봤으며, 이를 통해 젠킨스를 설치했습니다.
그리고 CI/CD를 젠킨스의 대표적인 아이템인 Freestyle과 Pipeline을 통해 구현해 봤으며, 단순히 CI/CD만을 구현하는 것이 아니라 다양한 플러그인을 사용해 GitOps의 기능 또한 구현해 봤습니다.
지금까지 우리는 쿠버네티스 환경을 구축하는 것에 중점을 두었습니다.
구축이 끝났다면 이러한 환경을 상용으로 넘기게 되는데, 이때 가장 중요한 부분 가운데 하나가 시스템을 관측하는 모니터링입니다!

다음 포스팅에선 지금까지 구축하고 사용한 쿠버네티스 환경을 어떻게 하면 효과적으로 모니터링할 수 있을지 알아보겠습니다.
지금까지 고생하셨습니다 :)


본 게시물은 "컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커 - 조훈,심근우,문성주 지음(2021)" 기반으로 작성되었습니다.

profile
MLOps, MLE 직무로 일하고 있습니다😍

0개의 댓글