[Jenkins] 도커/쿠버네티스에서 파이프라인 구축 & kaniko & ArgoCD

신현식·2023년 3월 8일
0

구름_Jenkins

목록 보기
3/3
post-thumbnail

도커에서 pipeline 구축

pipeline {
  agent none 
  
  triggers {
    pollSCM '* * * * *'
  }

  parameters {
    string name: 'IMAGE_NAME', defaultValue: 'hello-world'
    string name: 'IMAGE_REGISTRY_ACCOUNT', defaultValue: 'shinhyeonsik'
  }

  stages {
    stage('SCM Checkout') {
      agent any
      steps {
        git branch: 'main', url: 'https://github.com/hsshin0602/source-maven-java-spring-hello-webapp.git'
      }
    }
    
    stage('Build Maven Project') {
      agent { 
        docker {image 'maven:3-openjdk-8'} 
      }
      steps {
        sh 'mvn clean package -DskipTests=true' 
      }
    }
    
    stage('test Maven Project ') {  
      agent {
        docker { image 'maven:3-openjdk-8'}
      }
      steps {
        sh 'mvn test'
      }
    }

    
    stage('Build Docker Image') {
      agent any
      steps {
        sh "docker image build -t ${params.IMAGE_NAME} ."
      }
    }

    stage('Tagging Docker Image') {
      agent any
      steps {
        sh "docker image tag ${params.IMAGE_NAME} ${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}"
      }
    }

    stage('Publish Docker Image') {
      agent any
      steps {
        withDockerRegistry(credentialsId: 'docker-hub-token', url: 'https://index.docker.io/v1/') {
          sh "docker image push ${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}"
        }
      }
    }
  }
}

# 각 단계마다 컨테이너를 띄우야 하기 때문에 각 스테이지마다 필요할 경우 설정
# 테스트 생략옵션: -DskipTests=true
# 빌드와 테스트는 분리시키는게 좋음
# image 빌드와 tagging를 같이해도 됨


# 도커 이미지 빌드를 위한 도커파일 생성
vi Dockerfile

FROM tomcat:9-jre8
COPY target/hello-world.war /usr/local/tomcat/webapps
  • 도커허브 저장소에 push하기 위해서는 도커허브에서 설정을 해줘야한다.
    AccountSetting -> Security -> New Access Tocken
    이름: For Jenkins
    설정: Read & Write
    생성한 이후 토큰값은 복사해서 저장해둔다. 화면을 닫으면 사라지므로 주의
  • 젠킨스에서 토큰을 가져올 수 있는 자격증명 만들기
    젠킨스 관리 -> Manage Credentials -> global -> add Credentials
    Kind: Username with password선택
    username: 도커허브 계정(shinhyeonsik)
    Secret: 도커허브에서 생성한 토큰값
    ID: docker-hub-token
  • docker login를 실행해주는 명령 생성
    pipeline-syntax -> 스니펫
    withDockerRegistry: Sets up Docker registry endpoint 선택
    Docker registry URL: https://index.docker.io/v1/
    Registry credentials: 생성한 docker_hub_token 선택
    -> 생성한 스니펫을 위에 jenkinsfile-docker의 push 부분에 명령으로 추가
  • 새로운 프로젝트 생성
    이름: maven_docker_pipeline, pipeline 유형 선택
    SCM: git, 저장소 주소 가져오기, main
    Script Path: jenkinsfile-docker
    -> 처음에는 트리거가 작동되지 않음으로 지금 빌드를 해준다.
  • 성공적으로 작동하게되면 도커허브에 생성한 이미지가 저장되어있는 것을 확인할 수 있다.
  • 생성한 이미지를 가져와서 run 해보기, 현재 젠킨스가 8080포트이기 떄문에 80으로 포트 포워딩 해준다.
# 원도우에서 인스턴스에 ssh로 접속

# 80으로 포트포워딩
docker container run -d --name myweb -p 80:8080 shinhyeonsik/hello-world

# 작동 확인
docker container ls

# ec2 인스턴스의 퍼블릭 IP:hello-world으로 접속하면 접속됨

index.jsp 내용의 version을 수정(1.0.3 -> 1.0.4)한 이후 깃에 푸쉬를 진행, 파이프라인을 통해 자동으로 이미지를 재생성을 진행, 기존 컨테이너를 지우고 그 이미지로 컨테이너를 실행시켜보면 수정된 버전으로 내용이 나오지 않음, 즉 이미지가 작동하지 않음
이유: docker image ls로 확인했을때 로컬에 있는 것은 1.0.3 이고 도커 허브에 있는 것이 1.0.4이다. 이는 docker container의 옵션에서 pull이라는 옵션이 있는데 defalut값이 missiong이다.
이는 같은 이미지가 있으면 새롭게 풀링하지 않겠다는 의미인데 이를 always로 세팅하면 된다. 하지만 이렇게 진행하면 pulling을 네트워크 트래픽 비용이 많이 발생해서 좋지않다.

docker container run -d --name myweb -p 80:8080 --pull always shinhyeonsik/hello-world
  • latest 태그는 최신의 이미지라는 의미이다. 하지만 실제로 테스트하는 환경에서만 latest를 사용한다. 왜나하면 수시로 바뀌는 이미지이기 때문이다. 따라서 latest 이외의 Build_Number로 태그를 붙인 것을 추가해주고 이 이미지를 사용해서 이미지를 pulling하면 된다.
    Docker 이미지 태그에 Semver 사용

  • 태그 붙이기: env.BUILD_NUMBER로 이미지 별로 구분이 가능하기 때문에 이를 사용


# 밑 내용만 수정
...
stage('Tagging Docker Image') {
      agent any
      steps {
        sh "docker image tag ${params.IMAGE_NAME} ${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}:${env.BUILD_NUMBER}"
        sh "docker image tag ${params.IMAGE_NAME} ${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}:latest"
      }
    }

    stage('Publish Docker Image') {
      agent any
      steps {
        withDockerRegistry(credentialsId: 'docker-hub-token', url: 'https://index.docker.io/v1/') {
          sh "docker image push ${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}:${env.BUILD_NUMBER}"
          sh "docker image push ${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}:latest"
        }
      }
    }
    ...

도커 컨테이너를 실행하는 것은 복잡한 단계가 필요하고 쿠버네티스로 배포 할 것이기 때문에 이미지를 빌드하는 것까지만 파이프라인으로 실행한다.

쿠버네티스에 젠킨스 배포

  • Deploy EKS
eksctl create cluster --name myeks --region ap-northeast-2 --version 1.24 --instance-types t3.medium

eksctl utils associate-iam-oidc-provider --cluster myeks --approve

# SA 계정 생성, EBS add-on을 추가해서 스토리지 사용가능
eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster myeks \
  --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole

# ACCOUNT_ID 확인
aws sts get-caller-identity --output text

# ID 넣고 진행
eksctl create addon --name aws-ebs-csi-driver --cluster myeks --service-account-role-arn arn:aws:iam::<ACCOUNT_ID>:role/AmazonEKS_EBS_CSI_DriverRole --force
# 네임스페이스 생성 및 레포 생성
kubectl create namespace jenkins
helm repo add jenkinsci https://charts.jenkins.io
helm repo update


# 작업할 디렉터리 생성
mkdir jenkins_with_eks
cd jenkins_with_eks

# 파일 작성, 계정 및 플러그인 자동 구성
vi jenkins-values.yaml

controller:
  tag: "lts-jdk11"
  serviceType: LoadBalancer
  installPlugins:   
  - kubernetes
  - workflow-aggregator
  - git
  - configuration-as-code
  - pipeline-stage-view
  adminPassword: "P@ssw0rd"
persistence:
  storageClass: "gp2"


# 설치 진행
helm install jenkins jenkinsci/jenkins -n jenkins -f jenkins-values.yaml

# 젠킨스 배포 확인
kubectl get all -n jenkins

플러그인 설치가 오래걸려서 배포하는데 시간이 걸림

  • 헬름으로 배포한 것은 계정만들고 플러그인 설치를 따로 해줄 필요가 없음.
    서비스IP:8080으로 접속하면 젠킨스페이지에 접속가능하다. 계정: admin / 패스워드: P@ssw0rd
# 서비스 IP 확인, 포트 8080
kubectl get svc

젠킨스관리 -> 노드관리 -> Configure Clouds를 보면 쿠버네티스가 자동으로 세팅되어 있다. test connection 또한 잘 된다.
원리: jenkins 파드-> api서버에 통신을 하기위한 주소가 Kubernetes URL에 나와있다. 이 주소는
default 네임스페이스에 존재하는 kubernetes란 서비스이다. 엔드포인트로 확인했을 때 나와있는 IP가 API서버를 의미. jenkins에 권한이 있는 SA가 세팅되어있고 그 권한으로 api서버에 접근하는 것이다.

  • 밑에 작성한 파이프라인 파일은 이미지 빌드하기 전까지 실행시킨다. 쿠버네티스로 파드를 만들어서 maven 프로젝트를 빌드하고 테스트까지 진행한 것이다.
cd ~/git/source-maven-java-spring-hello-webapp


vi Jenkinsfile-k8s

pipeline {
  agent {
    kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3-openjdk-8
    command: ['sleep']
    args: ['infinity']
'''
    }
  }

  triggers {
    pollSCM '* * * * *'
  }

  parameters {
    string name: 'IMAGE_NAME', defaultValue: 'hello-world'
    string name: 'IMAGE_REGISTRY_ACCOUNT', defaultValue: 'shinhyeonsik'
  }

  stages {
    stage('SCM Checkout') {
      steps {
        container('maven') {
          git branch: 'main', url: 'git@github.com:hsshin0602/source-maven-java-spring-hello-webapp.git'
        }
      }
    }

    stage('Build Maven Project') {
      steps {
        container('maven') {
          sh 'mvn clean package -DskipTests=true'
        }
      }
    }

    stage('Test Maven Project') {
      steps {
        container('maven') {
          sh 'mvn test'
        }
      }
    }
  }
}
  • 쿠버네티스는 파드를 detach모드로 작동시키는데 특정 이미지들은 sh를 실행시킨다. 이를 detach모드로 실행시키기 위해 CMD와 Entrypoint를 지정해둔다.

쿠버네티스 CI/CD에서 도커 이미지를 빌드하는 방법

쿠버네티스 기반에 설치된 젠킨스와 젠킨스 에이전트는 파드롤 실행되고, 이런 파드에서 이미지 빌드가 되어야 되는 상황이다. 하지만 쿠버네티스 파드 내에서는 도커 데몬에 접근하기 위한 권한이 없기 때문에 도커 명령을 실행시키지 못한다. eks dind는 보안에 좋지 못하기 때문에 사용하지 않는다.

해결방안
kaniko: 도커 데몬 없이 이미지 만들기, 구글 오픈소스 프로젝트로 도커 데몬에 의존없이 이미지를 만들 수 있다.
kaniko 깃 저장소

  • kaniko Build Contexts : kaniko에서 컨테이너 이미지를 빌드하기 위한 Dockerfile을 가져올 수 있다.
    kaniko 빌드 컨텍스트
  • Kubernetes cluster에 kaniko를 실행시키기 위한 요구사항으로 Standard Kubernetes cluster (e.g. using GKE)
    Kubernetes Secret, A build context 조건이 있다.
    Running kaniko In a Kubernetes cluster

Kubernetes Secret: Kubernetes 클러스터에서 kaniko를 실행하려면 표준 실행 Kubernetes 클러스터와 최종 이미지를 푸시하는 데 필요한 인증이 포함된 Kubernetes 암호가 필요

  • kaniko는 이미지 빌드하는 기능과 이미지를 저장소에 push하는 기능까지 다 가지고 있음
# 시크릿 생성
kubectl create secret docker-registry regcred -n jenkins \
--docker-username=shinhyeonsik \
--docker-password=<토큰값> \
--docker-server=https://index.docker.io/v1/


vi Jenkinsfile-k8s

pipeline {
  agent {
    kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3-openjdk-8
    command: ['sleep']
    args: ['infinity']
  - name: kaniko
    iamge: gcr.io/kaniko-project/executor:debug
    command: ['sleep']
    args: ['infinity']
    volumeMounts:
      - name: registry-credentials
        mountPath: /kaniko/.docker
  volumes:
    - name: registry-credentials
      secret:
        secretName: regcred
        items: 
        - key: .dockerconfigjson
          path: config.json
'''
    }
  }

  triggers {
    pollSCM '* * * * *'
  }

...
 
    stage('Build and Tag Docker Image') {
        steps {
            container('kaniko') {
              sh "executor --dockerfile=Dockerfile \
              --context=dir://${env.WORKSPACE} \
              --destination=${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}:${env.BUILD_NOMBER} \
              --destination=${params.IMAGE_REGISTRY_ACCOUNT}/${params.IMAGE_NAME}:latest"
            }
        }
    }
  }
}

kaniko 이미지는 gcr.io/kaniko-project/executor:debug, 디버깅이 가능한 이미지를 추천한다.

Argo CD

젠킨스에서 CD도 가능하지만 쿠버네티스에 오브젝트를 배포하는 오픈 소스 도구인 Argo CD를 사용할 것이다.

  • 선언적 GitOps지속적 배포

  • GitOps란?

    • Weaveworks에서 처음 사용하기 시작
    • Git에서 시작하여 Git으로 끝나는 접근 방식
    • SSOT : Single Source Of Truth, 신뢰 가능한 단일 소스
    • 모든 운영 환경은 Git에 존재
    • Git에서 모든 변경사항을 관찰 및 검증 가능
  • Git 저장소에 선언적인 쿠버네티스 오브젝트(리소스,yaml형식) 존재

  • 해당 소스를 쿠버네티스 클러스터에 배포

  • 오브젝트 소스 종류

Argo CD 참조사이트

  • Argo CD 설치
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
  • 서비스 중에 argocd-server를 통해 접속해야 하는데 현재 ClutserIP로 설정되어있다. 따라서 포트포워딩을 하거나 patch를 진행시켜줘야한다.
# 로드밸런서 외부용 IP 확인
kubectl get svc -n argocd

# 로드밸런서로 바꿈
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
  • 처음에 접속하면 로그인을 해야한다. 계정: admin, 패스워드는 랜덤으로 설정되므로 아래 명령을 통해 확인해야함.
    로그인 후 User Info에서 패스워드 변경가능
# 패스워드 찾기 
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo      

ArgoCD로 배포

kaniko를 이용해서 이미지를 빌드하고 push까지 진행하였다. 이후 이미지를 사용할 수 있도록 deployment와 서비스를 실행할 파일을 만들 별도의 레포지토리를 파서 따로 만든다.

새로운 git repo , workdir 생성 및 remote로 연결

  • deployment.yaml 파일 작성
cd ~/git
mkdir hello-kube
cd hello-kube
git init


vi deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: hello-world
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: shinhyeonsik/hello-world:4   # 내가 만든 이미지 작성
          ports:
            - containerPort: 8080
  • services.yaml파일 작성
vi services.yaml
apiVersion: v1
kind: Service
metadata:
  namespace: default
  name: hello-world
spec:
  type: LoadBalancer
  selector:
    app: hello-world
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  • 두개의 yaml파일 생성 후 새로운 repo에 push

git add.
git branch -M main
git commit -m 'Init'
git remote add origin git@github.com:hsshin0602/hello-kube.git
git push -u origin main

ArgoCD접속 및 새로운 APP생성

  • application name: ArgoCD에서 어플리케이션의 이름
  • Project name: argocd에서 네임스페이스처럼 구분하기 위한 단위
  • 동기화 정책은 Automatic, sync가 자동화이기떄문에 깃에 커밋이 있는 경우 자동으로 파일을 최신화한다.

  • source 주소: 배포용 저장소
  • 루트경로에 있는 mainfast 파일 사용

  • 목적지는 쿠버네티스 api서버

앱을 생성하게 되면 동기화 되었다고 나오는데 깃 저장소에 있는 매니페스트 파일을 ArgoCD가 가져왔다는 것을 의미한다.

git 저장소의 yaml파일들이 실행되어 자동으로 파드와 서비스, deplyment 등이 만들어진다.
[이미지]

Jenkinsfile 수정

배포용 저장소에 있는 deployment의 컨테이너 이미지를 새로 생성한 이미지로 자동으로 교체를 해야한다. 이를 젠킨스파일에서 배포용 저장소를 가져오고 파일을 수정한 뒤 다시 푸쉬를 진행해주면 된다.

vi Jenkinsfile-k8s

...
stage('Update Kubernetes Manifests') {
      steps {
        container('maven') {
          git branch: 'main', url: 'https://github.com/hsshin0602/hello-kube.git'
          sh 'sed -i "s/image:.*/image: ${IMAGE_REGISTRY_ACCOUNT}\\/${IMAGE_NAME}:${BUILD_NUMBER}/g" deployment.yaml'
          sh 'git add deployment.yaml'
          sh 'git config --global user.name shin'
          sh 'git config --global user.email hsshin0602@naver.com'
          sh 'git commit -m "Jenkins Build Number - ${BUILD_NUMBER}"'
          withCredentials([gitUsernamePassword(credentialsId: 'github-credential', gitToolName: 'Default')]) {
            sh 'git push origin main'
          }
        }
      }
    }
  • 새로 생성한 git주소를 checkout
  • sed 명령어로 deployment.yaml의 image를 dockerhub에 새로 추가된 이미지로 교체
  • git에 commit을 하기 위해서 config --global로 이름과 이메일을 등록
  • jenkinsfile을 작업하는 것은 jenkins이고 image를 빌드 후 docker hub에 올리면 최종적으로 해당 이미지를 새로운 git의 deployment.yaml에 적용 후 commit과 push를 해야한다.
  • git저장소에 push하기 위해선 jenkins가 git저장소에 접근할 수 있는 권한이 필요하다.

  • git-> settings -> developer settings
    personal access token에서 새로운 token(classic) 생성(repo권한만 체크) 후 값 저장

  • jenkins관리 -> manage credential -> global -> add credential
    kind: Username with password
    username : hsshin0602 (git사용자 이름)
    password : git에서 생성한 토큰 복사
    id : github_credential (구분하기 위한 이름)

최종적으로 빌드를 하게되면 도커허브에 이미지가 새롭게 생성되고 이후 deployment.yaml의 컨테이너의 이미지를 결정하는 부분을 새 이미지로 변경한 뒤 commit과 push가 된다.

  • commit이 refresh를 하게되면 be에서 a9로 sync됨

deployment로 배포된 파드와 해당 파드를 외부로 노출시키는 로드밸런서 타입의 서비스가 존재한다. 브라우저에 로드밸런서의 외부용ip/hello-world를 입력하면 원하는 웹페이지가 나온다.

쿠버네티스 파이프라인 과정 정리

  • jenkins파일에 쿠버네티스로 필요한 이미지를 가진 컨테이너들의 정의해둔다.
    maven 서버가 필요하면 maven 이미지로 생성된 컨테이너를 임시적으로 생성하여 mvn clean package, test 등의 작업을 수행을 한다. 이후 컨테이너(= 노드)는 종료된다. 영구적으로 파드를 띄우는 것이 아닌 작업을 진행할때만 실행했다 종료하는 것이다.

  • 이후 Dockerfile을 작성하는데 tomcat서버를 구축하고 생성된 war 실행파일을 webapps디렉터리에 복사하는 작업을 하도록 구성한다.

  • kaniko를 사용해 Dockerfile을 빌드하여 이미지를 생성한다.

  • 배포용 저장소를 새롭게 만들고 안에 배포를 위한 서비스와 디플로이먼트 등 yaml파일을 만든다.(deployment ,services..) ArgoCD에 배포 용 저장소을 등록해서 이 리소스들을 배포할 수 있게 한다.

  • 이때 개발자의 코드가 수정되어 kanino에 의해 새로운 버전의 이미지가 생성되면 ArgoCD가 모니터링하고 있는 git의 deployment.yaml의 image를 새로운 버전의 이미지로 바꾼다.

  • ArgoCD에서 보면 자동으로 commit , push된 deployment.yaml를 감지한다. 그 후 새로운 레플리카셋을 만들어지고 새로운 이미지가 적용된 컨테이너를 가진 파드를 생성한다. 기본적으로 deployment이기 때문에 기본 전략인 롤링 업데이트되는 것이다.

프로메테우스에서 젠킨스의 상태를 대쉬보드를 확인가능
Jenkins Prometheus Metrics Plugin

profile
전공 소개

0개의 댓글