웹 풀사이클 데브코스 TIL [Day 74] - 젠킨스(Jenkins)

JaeKyung Hwang·2024년 3월 11일
0
post-thumbnail

2024.03.11(월)

🎬CI/CD 시나리오

  • CI (Continuous Integration; 지속적 통합) 단계
    • 일반적으로 개발자가 소스 코드를 커밋하고 푸시하는 것으로 시작
    • 응용 소프트웨어를 자동으로 빌드, 통합
    • (자동) 테스트를 통하여 배포할 수 있는 상태임을 확인
  • CD (Continuous Delivery/Deployment; 지속적 인도) 단계
    • CI 단계에서 소프트웨어가 배포 가능한 상태임을 확인하는 것으로 시작
    • 응용 소프트웨어를 컨테이너 이미지로 만들어 냄
    • 포드, 티플로이먼트, 서비스 등 다양한 오브젝트 조건에 맞추어 (미리 설정한 파일을 통해) 배포

👔Jenkins

  • 자바(Java)로 작성된 오픈 소스 자동화 서버
  • 지속적 인도 프로세스를 구축하는 데 널리 이용됨: 유연성과 확장성이 뛰어남
  • 허드슨(Hudson)이라는 이름으로 공개된 바 있으나 오라클이 허드슨을 인수하고 젠킨스(Jenkins)로 이름을 바꿈

💡특징

  • 다양한 프로그래밍 언어 지원
  • 플러그인을 통한 확장
    • 사용자가 직접 플러그인을 작성해 젠킨스의 기능을 확장하는 것도 가능
  • 이식성
    • 여러 종류의 컴퓨터에서뿐만 아니라 컨테이너 및 클러스터 환경에도 부드럽게 적용
  • 대부분의 소스 관리 시스템 지원
  • 분산 처리 지원
    • 마스터/슬레이브 구조를 채택하여 여러 노드에서 작업 수행
  • 코드로 파이프라인 구성
    • 프로세스 자동화에 적합

🏗️아키텍처

  • 마스터-슬레이브 구조
    • 슬레이브(slave)는 에이전트(agent)라고 부르기도 함
  • 마스터
    • 빌드 시작 트리거 포착 (ex: 코드 커밋)
    • 알림 (ex: 빌드 실패를 사용자에게 slack으로 전달)
    • 클라이언트와 통신하며 HTTP 요청 처리 (웹에서 상호작용 가능)
    • 에이전트에서 실행 중인 작업의 우선순위 조정 등 빌드 환경 관리
  • 에이전트
    • 마스터에 의한 개시 후 모든 작업 처리

↔️수평적 확장

  • 조직(개발팀, 테스트팀, 데브옵스팀)이 늘어날 때마다 마스터 인스턴스의 수를 늘려가는 방식

  • 통합 자동화가 복잡해진다는 단점이 있으나
    • 마스터 역할을 하는 컴퓨터의 하드웨어 사양에 대한 부담 감소
    • 팀마다 각기 다른 설정이 가능
    • 팀 전용 마스터 인스턴스가 있으므로 팀워크와 업무 효율이 높아짐
    • 마스터 인스턴스 하나에 문제가 생겨도 다른 팀에 끼치는 영향이 최소화됨

📥k8s 클러스터에 소프트웨어 설치

🚢Helm

대표적인 k8s용 패키지 매니저

  • 오브젝트 배포에 필요한 사양이 이미 정의된 차트(chart)를 이용하여 패키지를 검색하고 내려받아 설치
  • 공개되어 있는 소프트웨어 패키지를 k8s에 배포하는 것 외에도 배포 효율화를 위해 많이 이용되는 방법
  • RedHat 계열의 rpm, yum 또는 Debian 계열의 apt와 비슷한 방식으로 동작
  • 설치

✅helm으로 젠킨스 설치

  • Docker Desktop 구동해두어야 함
    helm repo add jenkinsci https://charts.jenkins.io
    helm repo update
    helm install jenkins jenkinsci/jenkins

  • 젠킨스 설치 확인
    • 로그 확인
      kubectl logs sts/jenkins jenkins
    • 포트 포워드를 해서 k8s 클러스터 내에서 8080 포트로 제공되고 있는 jenkins 서비스에 로컬 컴퓨터의 포트를 연결 (명령이 실행되고 있는 중에만 포트 포워딩됨)
      kubectl port-forward svc/jenkins 8080:8080

⚙️젠킨스 기초 설정

🗝️관리자 비밀번호 알아내기

  • 설치된 Jenkins의 관리자 계정 (admin) 설정은 k8s의 secret 오브젝트로 관리됨
  • 다음 명령어로 비밀번호를 알아낼 수 있음 (초기 비밀번호 설정은 설치할 때마다 다름)
    kubectl exec --namespace default -it svc/jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password
  • 알아낸 비밀번호로 로그인

🔑관리자 비밀번호 변경

⚠️ Jenkins 메뉴 (Users > Jenkins Admin > Configure)에서 변경한 사항은 포드가 새로 실행할 때 적용이 되지 않아 원래의 비밀번호로 되돌려짐

  • k8s 오브젝트(secret)를 변경해서 admin(Jenkins Admin) 계정의 비밀번호를 적용해야 함
    • secrets 오브젝트 조회 & 수정
      kubectl get secrets jenkins
      kubectl edit secrets jenkins
    • editor에서 다음 부분을 수정 (단, base64로 인코딩해서 적어야 함)
      data:
      	jenkins-admin-password: [your base64-encoded password]
      	jenkins-admin-user: YMRtaW4=

      💡 git bash에서 echo "변환할 문자" | base64 | clip로 encoding 및 결과 복사 가능!

  • jenkins-0라는 이름의 pod를 삭제 → k8s에 의해 pod 재시작(STATUS = Ready까지 시간이 좀 걸림) → 변경된 admin 비밀번호로 로그인 가능!

🔤언어 설정

  • 영어 사용
  • Manage Jenkins > Plugins > Available plugins에서 플러그인 “Locale” 검색 & 설치
  • Manage Jenkins > System > Localeen_US 입력 & 박스 체크하고 저장

⌚시간대 설정

  • 자신이 살고 있는 지역의 시간대로 서버 시간대를 설정
  • Manage Jenkins > People > [계정 선택] > Configure > User Defined Time Zone에서 Asia/Seoul 설정 후 저장

🌱젠킨스 기본 사용법 맛보기

💬Hello World

사실은 빌드 및 배포 등에 대해서는 아직 아무런 하는 일이 없는 예제이지만 Jenkins “Item”을 생성하고 이것이 어떤 방식으로 동작하는지를 엿보는 목적으로 테스트
Jenkins는 “Item” 단위로 프로젝트를 관리 (웹 사용자 인터페이스를 이용해 설정할 수 있고 상태를 열람할 수 있도록 되어 있음)

  • 젠킨스 접속(http://localhost:8080/) > Dashboard 왼쪽 위 ➕New Item
  • Item name 입력 (hello-world), Pipeline 선택 후 OK

  • Pipeline에서 Definition은 Pipeline script 선택 후 try sample Pipeline… 목록에서 Hello World 선택 후 Save
  • ▶️Build Now > 왼쪽 아래 # 누르고 Console Output에서 Hello World 확인
    Running ondefault-54p3x in /home/jenkins/agent/workspace/hello-world
    [Pipeline] {
    [Pipeline] stage
    [Pipeline] { (Hello)
    [Pipeline] echo
    **Hello World**
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] End of Pipeline
    Finished: SUCCESS

🧐클러스터 내 에이전트 동작 관찰

  • Jenkins 마스터는 k8s 클러스터 내에 동적으로 에이전트를 생성, 즉 프로비저닝(provisioning)
    • 이를 위해서 k8s 플러그인이 설치되어 있어야 하며, 적절한 설정도 되어 있어야 하지만 helm을 이용해 Jenkins를 설치할 때 이러한 설정들도 기본값으로 구성되어 있음
  • 동적 에이전트 프로비저닝 테스트
    • (인위적으로) 빌드 과정이 오래 걸리는 파이프라인 구성
      • Dashboard > hello-world > Configure > Pipeline Script에 다음과 같이 sleep 300 코드 추가
        Hello World를 출력하기 전에 5분 쉬도록 해서 인위적으로 빌드 과정이 오래 걸리도록 함
        ```
        pipeline {
            agent any
        
            stages {
                stage('Hello') {
                    steps {
                        sleep 300
                        echo 'Hello World'
                    }
                }
            }
        }
        ```
    • 순차적으로 여러 차례의 빌드 스케줄 (▶️Build Now 버튼 여러 번 클릭)
      • 지금은 수동으로 빌드를 개시했지만 보통은 SCM(ex. git) repository에 발생하는 이벤트(ex. push)에 의해 트리거됨
    • Jenkins의 동작과 k8s 클러스터의 상태 관찰
      • 5번 Build 했더니 5개의 pods가 생성되었음
      • 작업이 완료되고 에이전트(pod)는 종료됨
        Running ondefault-l8xj6 in /home/jenkins/agent/workspace/hello-world
        [Pipeline] {
        [Pipeline] stage
        [Pipeline] { (Hello)
        [Pipeline] sleep
        **Sleeping for 5 min 0 sec**
        [Pipeline] echo
        **Hello World**
        [Pipeline] }
        [Pipeline] // stage
        [Pipeline] }
        [Pipeline] // node
        [Pipeline] End of Pipeline
        Finished: SUCCESS
  • Dashboard > Manage Jenkins
    • Plugins에서 Kubernetes plugin이 이미 설치되어 있는 것을 확인할 수 있다. (thanks to helm)
    • Clouds > kubernetes > Pod Templates에는 default라는 pod template이 존재한다.
      • 기본적으로 에이전트가 작업을 수행할 때 이 pod template을 가지고 pod가 만들어진다.
      • 생성되는 pod 안에는 container가 한 개 들어가고 명시된 도커 이미지를 기반으로 빌드된다.

⌨️젠킨스 프로젝트 실습

간단한 웹 응용을 젠킨스에 의해 빌드 및 배포되도록 설정해보기

  • 프로젝트 소스(쿠버네티스를 이용한 서비스 운용 실습에서 이용했던 파일)가 담긴 GitHub 리포지토리 준비

🕺Freestyle project 이용

  • 젠킨스의 빌드 환경 설정
    • Dashboard > Manage Jenkins > Plugins에서 Docker PipelineKubernetes CLI 설치 & 젠킨스 재시작
    • 젠킨스 접속(http://localhost:8080/) > Dashboard 왼쪽 위 ➕New Item > Item name 입력 (simple-echo-1), Freestyle project 선택 후 OK

    • Source Code Management에서 Git 선택하고 Repository 주소(https://github.com/do0ori/jenkins-web-practice.git) 입력
      • public repo라 자격증명은 따로 필요 X
      • Branches to build에서 master → main으로 수정
    • Build Steps에서 Execute shell 선택 후 다음과 같이 Command 작성 후 Save (Docker를 이용해 이미지를 빌드하고 DockerHub 레지스트리에 푸시)
      docker build -t do0ori/simple-echo .
      docker push do0ori/simple-echo
  • ⛔Build Now 했을 때 실패
    Started by user Jenkins Admin
    Running as SYSTEM
    Agent default-7p4m7 is provisioned from template **default**
    ---
    ⁝
    + docker build -t do0ori/simple-echo .
    /tmp/jenkins7904157774705426922.sh: 2: **docker: not found**
    Build step 'Execute shell' marked build as failure
    Finished: FAILURE
    • default pod template의 도커 이미지에는 Docker CLI가 설치되어 있지 않기 때문 ⇒ Custom Agent를 등록하고 사용하도록 해야 함!
  • Dashboard > Manage Jenkins > Clouds > kubernetes > Pod Templates에서 Add a pod template 버튼 클릭
    • ⭐신규 추가하는 pod template에는 두 개의 컨테이너 스펙이 포함되도록 할 예정
      1. 빌드 작업을 실행할 컨테이너 (이름: jnlp)
        • JNLP(Java Network Launch Protocol)을 따라 Jenkins 마스터와 작업 조율하면서 빌드 작업 실행
        • docker CLI와 kubectl CLI를 갖추고 있음
        • 지금은 미리 만들어진 이미지 이용: sheayun/jnlp-agent-sample
      2. 도커 데몬(daemon)을 실행하는 컨테이너 (이름: dind)
        • Docker in Docker
        • 어느 클러스터에 있더라도 통일된 docker build 환경을 제공하기 위해 독립된 컨테이너로 제공 (ex. 현재 docker desktop으로 클러스터 제공하고 있는 호스트의 daemon을 이용하지 않음)
        • 이미지는 docker:latest 사용
    • New pod template settings 다음과 같이 작성
      Namesample
      Namespacedefault
      Labelsjenkins-sample-agent
      UsageOnly build jobs with label expressions matching this node
      Pod template to inherit fromdefault
      1. Container에서 Add Container > Container Template

        Namejnlp
        Docker imagesheayun/jnlp-agent-sample
        • Command to runArguments to pass to the command 항목은 비우기
        • Environment Variables > Add Environment Variable > Environment Variable (같은 pod에서 실행되는 다른 container에 docker host가 있음을 알려주기 위한 설정)
          KeyDOCKER_HOST
          Valuetcp://localhost:2375
      2. Container에서 Add Container > Container Template

        Namedind
        Docker imagedocker:latest
        Command to run/usr/local/bin/dockerd-entrypoint.sh
        • Arguments to pass to the command 항목은 비우기
        • Environment Variables > Add Environment Variable > Environment Variable (Docker deamon이 해당 환경 변수를 가지게 해 TLS 인증을 하지 않도록 하는 설정)
          KeyDOCKER_TLS_CERTDIR
          Value비우기
          • Advanced에서 Run in privileged mode 체크
    • Create를 눌러서 설정 저장
  • Dashboard > simple-echo-1 > Configuration
    • General 항목의 Restrict where this project can be run 체크
    • Label Expression에 jenkins-sample-agent 입력 (아까 만든 pod template의 label 이름)
    • Save로 저장
  • ⛔Build Now 했을 때 실패
    Started by user Jenkins Admin
    Running as SYSTEM
    Agent sample-trtm6 is provisioned from template **sample**
    ---
    ⁝
    **denied: requested access to the resource is denied**
    Build step 'Execute shell' marked build as failure
    Finished: FAILURE
    • template은 우리가 만든 sample을 잘 사용하고 있으나, docker push를 할 때 docker hub registry에 대한 권한/인증 정보가 없기 때문 ⇒ 추가 설정 필요
  • Dashboard > Manage Jenkins > Credentials
    • Stores scoped to Jenkins에서 Domains 항목의 (global) 클릭 → Add Credentials 클릭
      KindUsername with password
      UsernameDocker Hub ID
      PasswordDocker Hub Password
      IDdockerhub-credentials
      DescriptionDockerHub credentials for account
    • Create로 생성
  • Dashboard > simple-echo-1 > Configuration
    • Build Environment 항목의 Use secret text(s) or file(s) 체크
    • Add > Username and password (separated)
      Username VariableDOCKERHUB_USER
      Password VariableDOCKERHUB_PWD
      Credentials아까 생성한 credential 선택
    • Build Steps 항목의 Command를 다음과 같이 수정 (docker push 전에 로그인)
      docker build -t do0ori/simple-echo .
      docker login -u ${DOCKERHUB_USER} -p ${DOCKERHUB_PWD}
      docker push do0ori/simple-echo
    • Save로 저장
  • ✅Build Now 했을 때 성공! docker hub에 잘 push된 것을 확인할 수 있다.
  • kubectl을 이용해 레지스트리로부터 이미지를 가져다가 같은 클러스터에 배포 (및 노출)
    • Dashboard > simple-echo-1 > Configuration > Build Steps 항목의 Command를 다음과 같이 수정 (deployment 및 service 생성)
      docker build -t do0ori/simple-echo .
      docker login -u ${DOCKERHUB_USER} -p ${DOCKERHUB_PWD}
      docker push do0ori/simple-echo
      kubectl apply -f deployment.yaml
      kubectl apply -f service.yaml
      • Save로 저장
  • ⛔Build Now 했을 때 실패
    + kubectl apply -f deployment.yaml
    **Error from server (Forbidden): error when retrieving current configuration of:**
    Resource: "apps/v1, Resource=deployments", GroupVersionKind: "apps/v1, Kind=Deployment"
    Name: "dpy-hname", Namespace: "default"
    from server for: "deployment.yaml": deployments.apps "dpy-hname" is forbidden: User "system:serviceaccount:default:default" cannot get resource "deployments" in API group "apps" in the namespace "default"
    Build step 'Execute shell' marked build as failure
    Finished: FAILURE
    • kuternetes 클러스터 접근 권한이 없기 때문 ⇒ 추가 설정 필요
  • ~/.kube/config 파일을 복사해 kubeconfig라고 이름 짓고 server: https://kubernetes.default로 수정
  • Dashboard > Manage Jenkins > Credentials
    • Stores scoped to Jenkins에서 Domains 항목의 (global) 클릭 → Add Credentials 클릭
      KindSecret file
      ScopeGlobal
      Filekubeconfig 파일 추가
      IDKUBECONFIG
      Descriptionkubeconfig file for docker-desktop
    • Create로 생성
  • Dashboard > simple-echo-1 > Configuration
    • Build Environment 항목의 Configure Kubernetes CLI (kubectl) with multiple credentials 체크
      Credentials아까 생성한 kubeconfig 선택
    • Save로 저장
  • ✅Build Now 했을 때 성공!! 드디어 성공..
    + kubectl apply -f deployment.yaml
    deployment.apps/**dpy-hname** created
    + kubectl apply -f service.yaml
    service/**svc-hname** created
    [kubernetes-cli] kubectl configuration cleaned up
    Finished: SUCCESS

🚀Pipeline 이용

  • 앞선 프로젝트와 비슷한 것을 파이프라인으로 실습
    • 간단한 파이프라인을 구성하고 이에 의해서 다음 과정이 수행되도록 실험
      • docker build && push
      • kubectl에 의해 deployment 및 service 생성
    • 위 파이프라인 정의를 Jenkinsfile로 만들고 이것을 리포지토리에 등록하여 빌드 과정 자동화
  • 젠킨스 접속(http://localhost:8080/) > Dashboard 왼쪽 위 ➕New Item > Item name 입력 (simple-echo-2), Pipeline 선택 후 OK
    • Pipeline script를 다음과 같이 작성 (Freestyle project에서 했던 과정을 모두 script로 만들었다고 보면 됨)
      pipeline {
          environment {
              dockerImageName = "do0ori/simple-echo"
          }
          
          agent {
              kubernetes {
                  yaml '''
                  apiVersion: v1
                  kind: Pod
                  spec:
                    containers:
                    - name: jnlp
                      image: sheayun/jnlp-agent-sample
                      env:
                      - name: DOCKER_HOST
                        value: "tcp://localhost:3275"
                    - name: dind
                      image: docker:latest
                      command:
                      - /usr/local/bin/dockerd-entrypoint.sh
                      env:
                      - name: DOCKER_TLS_CERTDIR
                        value: ""
                      securityContext:
                        privileged: true
                  '''
              }
          }
          
          stages {
              stage("git scm update") {
                  steps {
                      sh "git clone https://github.com/do0ori/jenkins-web-practice.git ."
                  }
              }
              
              stage("docker build && push") {
                  steps {
                      script {
                          dockerImage = docker.build dockerImageName
                          docker.withRegistry(
                              "https://registry.hub.docker.com",
                              "dockerhub-credentials"
                          ) {
                              dockerImage.push("latest")
                          }
                      }
                  }
              }
              
              stage("deploy application on kubernetes cluster") {
                  steps {
                      withKubeConfig([
                          credentialsId: "KUBECONFIG",
                          serverUrl: "https://kubernetes.default",
                          namespace: "default"
                      ]) {
                          sh '''
                          kubectl apply -f deployment.yaml
                          kubectl apply -f service.yaml
                          '''
                      }
                  }
              }
          }
          
          post {
              always {
                  sh "docker logout"
              }
          }
      }
    • Save로 저장
  • ✅Build Now 했을 때 성공
  • script에서 sh "git clone https://github.com/do0ori/jenkins-web-practice.git ."checkout scm으로 수정해서 Jenkinsfile이라는 이름의 파일에 저장하고 github에 올리기
  • Pipeline의 Definition을 Pipeline script에서 Pipeline script from SCM으로 변경
    SCMGit
    Repository URLhttps://github.com/do0ori/jenkins-web-practice.git
    Branch Specifier*/main
    Script PathJenkinsfile
    • Save로 저장
  • ✅Build Now 했을 때 성공

🔧Jenkins 파이프라인의 구성

  • Jenkins 파이프라인 정의에는 두 가지 방식이 있음
    1. 선언적(declarative) 방식
      • 우리가 지금까지 실습한 방법
      • 빌드 단계(stage)를 선언적으로 정의하는 방식
    2. 스크립트(scripted) 방식
      • 아직까지는 마주친 적 없지만 앞으로 만나보게 될 것
      • 빌드 단계(stage)를 절차적으로 기술하는 (스크립트를 실행하는) 방식
  • 두 가지 방식으로 파이프라인 실습을 해봄
    • Jenkins UI에서 선언적 스크립트를 입력하는 방식
    • GitHub repo에 Jenkinsfile로 파이프라인 스크립트를 저장하는 방식
profile
이것저것 관심 많은 개발자👩‍💻

0개의 댓글