쿠버네티스 전문가 양성과정 12주차 2일(3/7)

최수환·2023년 3월 7일
0

Kubernetes

목록 보기
52/75
post-thumbnail

파이프라인

  • 파이프라인은 지속적 통합 및 지속적 전달/배포를 하기 위해 버전 제어에서 최종 목표까지 소프트웨어를 가져오는 자동화된 프로세스를 지원하는 기능/플러그인
  • 파이프라인은 DSL(Domain-Specific Language)를 통해 코드로 작성
  • Jenkinsfile은 "Pipeline-as-code"를 구현하기 위한 DSL로 작성된 텍스트 파일이며 Git과 같은 소스 제어 저장소에 커밋될 수 있다.
    • 스크립트 방식 : 초기방식, Groovy언어/명령 사용
    • 선언적 방식 : 현재 방식
  • 젠킨스 파이프라인의 CI/CD 예시

스크립트 방식

  • 비교적 적은 섹션과 설명
  • 비교적 더 풍부한 절차형 코드 사용 가능 , 프로그램 작성 방식
  • Groovy를 알아야 작성이 가능
  • Jenkins와 연관이 적다
  • 선언적 방식에 비해 복잡하다
  • node로 시작하면 스크립트 방식

선언적 방식

  • 일반적인 Jenkins의 웹 폼에서 구성하는 특정 구성과 작업을 정의
  • Jenkins의 웹폼과 비슷
  • 비교적 가독성이 더 좋다
  • 블루오션 인터페이스 사용 가능
  • 문법 확인 및 에러 확인이 비교적 쉽다
  • 반복되는 로직에 대한 지원이 없다
  • pipeline으로 시작되면 선언적 방식
    • 블록 : 괄호를 열고 닫은 구간, 시작과 끝이 있는 코드의 묶음
      -> pipeline 블록으로 시작된다
    • 섹션 : 파이프라인 흐름 내에서 하나 이상의 지시문 또는 스텝의 묶음
      • agent섹션 : 전체 파이프라인 또는 특정 단계가 실행되는 노드 지정 , 각 단계마다 실행되는 노드를 다르게 할 수 있다
      • stages섹션 : 하나 이상의 순서가 있는 stage 묶음
      • steps섹션 : stage 지시문에서 실행할 하나 이상의 작업
      • post섹션 : stages 또는 stage의 마지막에 실행할 추가 작업

📒 파이프라인 문법 참조

섹션

agent 섹션

stages/steps 섹션

  • git , sh , echo와 같은 DSL문장을 넣을 수 있다.

📒 파이프라인 스텝 참조/검색

post 섹션

  • stages 또는 stage 섹션 실행 후 실행할 추가 작업
  • 전통적인 프리스타일 잡의 빌드 후 처리 동작과 비슷
  • post블록에는 stages 또는 stage를 실행한 후 상태에 따라 실행 여부를 결정

지시문

environment : 환경 변수 정의, 자격증명 참조

options : 빌드 옵션

  • disableConcurrentBuilds : 파이프라인 동시 빌드 금지
  • newContainerPerStage : 도커에서 단계마다 새로운 컨테이너 생성
  • retry : 파이프라인 재시도 횟수 지정
  • skipStagesAfterUnstable : 빌드 상태가 불안정하면 단계를 건너뜀
  • timeout : 파이프라인 실행에 대한 타임아웃 지정
  • timestamps : 콘솔 출력에 시간 정보 추가

parameters : 파이프라인에서 정의 및 참조할 수 있는 파라미터

  • string , text , booleanParam , choice , password

triggers : 파이프라인을 자동으로 실행 할 트리거

  • cron : 파이프라인이 트리거 되어야 하는 주기

    	triggers { cron('H */4 * * 1-5') }    
  • pollSCM : 새 소스 변경 사항이 있는지 확인하는 주기

    	triggers { pollSCM('H */4 * * 1-5') }
    • H(ash)는 균일한 부하를 생성하기 위함
    • 0 0 * * * 주기를 가지고 있는 파이프라인이 여러개인 경우 특정 시각에 부하가 몰리게 됨
  • upstream : 특정 파이프라인 잡이 실행되면, 후속으로 파이프라인 잡을 실행할 수 있음

    	triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }
  • githubPush : GitHub저장소의 웹훅(WebHook)에 의해 트리거

    • Github Integration 플러그인 설치
    	triggers { githubPush() }
    • Github에도 따로 웹훅을 설정해야함
    • pollSCM은 주체가 Jenkins가 되어 Jenkins가 등록된 Git에 변경사항(새로운 커밋...)이 생겼는지 확인한다면 GitHubPush는 GitHub이 주체가 되어 자신의 변경사항(새로운 커밋...)에 대해 Jenkins에게 알려준다.

stage : stages섹션에 정의하는 작업 정의

tools : 파이프라인에서 사용할 도구 정의

  • Jenkins의 "글로벌 도구" 에서 정의된 도구를 파이프라인에서 사용할 도구로 명시
  • agent node인 경우 무시된다
  • maven , jdk , gradle

input : stage에서 사용자 입력 값 요청 정의, 자동화를 해칠 수 있다, 커스터마이즈에는 효과적

when : stage에서 step을 실행하기 위한 조건 정의

  • branch : 특정 브랜치인 경우

    when { branch 'master'}
  • environment : 환경 변수의 값과 일치하는 경우

    when { environment name: 'DEPLOY_TO', value: 'production' }
  • expression : Groovy표현식이 참인 경우

    when { expression { return params.DEBUG_BUILD } }
  • allOf : 여러 조건 모두 참인 경우(AND)

    when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
  • anyOf : 여러 조건 중 하나가 참인 경우(OR)

    when { anyOf { branch 'master'; branch 'staging' } }
  • not : 조건이 거짓인 경우

    when { not { branch 'master' } }

파이프라인 관련 내장 문서

Jenkins에는 다양한 파이프라인을 쉽게 생성할 수 있도록 문서 기능이 웹에 내장되어 있고, 플러그인을 설치하면 내장 문서도 관련된 내용이 자동으로 업데이트 된다


Jenkins 파이프라인 생성

새로운 item클릭

  • 이름과 project(pipeline) 설정

  • 간단하게 테스트하기 위해 pipeline script선택
  • 일반적으로는 pipeline script from SCM을 선택한다.
    -> GIT을 등록해서 커밋된 Jenkinsfile을 가져온다

1 . Jenkinsfile을 이용한 간단한 war파일 배포

  • pipeline script선택
pipeline{
    agent{
        label 'jenkins-node'
    }
    stages{
        stage('Checkout'){
            steps{
                git branch: 'main', url: 'https://github.com/suhwan12/abc.git'
            }
        }
        stage('Build'){
            steps{
                sh 'mvn clean package'
            }
        }
        stage('Deploy'){
            steps{
                sh 'scp /var/lib/jenkins/workspace/test_pipeline/target/hello-world.war ubuntu@172.31.45.89:/var/lib/tomcat9/webapps'
            }
        }
    }
}
  • 정상적으로 빌드가 되어 war파일이 생성되고 tomcat서버에 배포가된다
  • scp의 프로젝트 경로에서 test_pipeline처럼 프로젝트 이름과 경로를 동일하게 맞춰주어야 한다

2 . github을 이용한 pipeline 생성

pipeline script from SCM 선택

  • git주소 입력
  • main으로 전환

git WorkDIR에서

vi Jenkinsfile
# 위의 코드를 붙여넣는다

git add .
git commit -m 'new pipeline'
git push
  • github에 push가 된것을 확인

Jenkins에서 다시 빌드해보면 스크립트를 쓰지 않았는데도 불구하고
외부(git-bash...)에서Jenkinsfile생성과 commit 및 github에 push를 통해서 등록한 git주소에 해당하는 git repo에 Jenkinsfile이 올라와 해당 Jenkinsfile이 빌드된다

📒 Jenkinsfile을 작성할때 파이프라인 관련 내장문서 사이트를 이용하면 좀 더 쉽게 작성할 수 있다.

3 . 트리거와 파라미터, env 사용

pipeline{
    agent{
        label 'jenkins-node'
    }
    triggers {
       pollSCM '* * * * *' # 트리거 사용 
    } 
    parameters { # 파라미터 사용 
      string defaultValue: '172.31.45.89', name: 'TOMCAT_IP'
      string defaultValue: 'ubuntu', name: 'TOMCAT_LOGIN_USER'
      string defaultValue: '/var/lib/tomcat9/webapps', name: 'TOMCAT_WEBAPP_DIR'
    }

    stages{
        stage('Checkout'){
            steps{
                git branch: 'main', url: 'https://github.com/suhwan12/abc.git'
            }
        }
        stage('Build'){
            tools{ # global 도구 사용 
              jdk 'Java-11'
              maven 'Maven-3'
            }
            steps{
                sh 'mvn clean package'
            }
        }
        stage('Deploy'){
            steps{
                sh "scp ${env.WORKSPACE}/target/*.war ${params.TOMCAT_LOGIN_USER}@${params.TOMCAT_IP}:${params.TOMCAT_WEBAPP_DIR}" 
            } # 설정한 param사용 , 큰 따옴표 사용 
        }
    }
}
  • jenkins파일을 만들고 처음 push하면 트리거를 만들었으므로 자동으로 jenkins가 변화를 감지하고 빌드할것이라고 생각하지만 jenkins는 아직 빌드한적이 없기 때문에 해당 jenkins파일에 트리거가 존재하는지 아닌지 모른다
  • 따라서 jenkins에 트리거를 걸려면 일단은 한번 수동으로 빌드를 해야한다.
  • 이후에 해당 Git의 변화(commit..)에 대해 트리거에 의해 자동으로 빌드를 하게 된다
  • 작은 따옴표를 사용하게 되면 '탈출'을 시킨다. 즉 문자열이 그 문자의 의미 그자체를 가지게 한다. 따라서 scp명령어를 사용하는 부분에서 변수를 출력하는데 작은 따옴표를 사용하게된다면 변수가 아닌 그 자체가 출력되어 오류가 발생한다.
    따라서 반드시 변수같이 문자와 문자의 의미와 다른 것들은 큰 따옴표를 사용해야 한다.

도커에서의 Jenkins 통합

인스턴스 생성 (jenkins-docker)

  • ubuntu 20.04
  • 스토리지 40G
  • 기존 보안그룹, 키페어
  • t3.medium
ssh -i pem키 ubuntu@퍼블릭ip # 인스턴스 접속 
sudo hostnamectl set-hostname jenkins-docker
exec bash # hostname 변경 

sudo apt update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg    

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null    

sudo apt-get update 
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 
#도커 설치 

sudo usermod -aG docker $USER
exit
#현재사용자를 docker그룹에 포함
  • docker는 sudo권한이 있어야하는데 일일이 sudo치기 비효율적이므로 그룹에 포함시킨다

📒 docker ubuntu 설치 참조


📒 install Jenkins with docker 참조

dokcer network create jenkins # 새로운 네트워크 생성
mkdir jenkins
cd jenkins  
# jenkins디렉터리 생성 및 이동 (필수 x)

docker container run --name docker-dind --detach \
--privileged --network jenkins --network-alias docker \
--env DOCKER_TLS_CERTDIR=/certs \
--volume jenkins-docker-certs:/certs/client \
--volume jenkins-data:/var/jenkins_home \
--volume docker:/var/lib/docker \
--publish 2376:2376 \
--restart=always \
docker:dind --storage-driver overlay2
# docker container생성 

vi Dockerfile # jenkins 이미지 생성하기 위한 Dockerfile 생성 

FROM jenkins/jenkins:lts-jdk11 
USER root
RUN apt-get update && apt-get install -y lsb-release
RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \   
  https://download.docker.com/linux/debian/gpg
RUN echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \
  https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list 
RUN apt-get update && apt-get install -y docker-ce-cli
USER jenkins
RUN jenkins-plugin-cli --plugins "docker-plugin docker-workflow"

docker image build -t jenkins-docker:lts-jdk11 .
# jenkins image 생성 

docker container run --name jenkins-docker \
  --restart=always \
  --detach \
  --network jenkins \
  --env DOCKER_HOST=tcp://docker:2376 \
  --env DOCKER_CERT_PATH=/certs/client \
  --env DOCKER_TLS_VERIFY=1 \
  --publish 8080:8080 \
  --publish 50000:50000 \
  --volume jenkins-data:/var/jenkins_home \
  --volume jenkins-docker-certs:/certs/client:ro \   
  jenkins-docker:lts-jdk11
# jenkins container 생성 
  • docker container ls 했을때 총 두개의 컨테이너가 실행되고 있어야 한다.
docker container exec -it jenkins-docker bash 
#jenkins컨테이너 접속
  • 접속 후 docker ps해보면 명령이 실행되는 것을 확인
    -> dind = docker안에서 docker가 실행된다

브라우저에 jenkins-docker 퍼블릭IP:8080입력

cat /var/jenkins_home/secrets/initialAdminPassword
# exec 명령으로 Jenkins컨테이너에 접속 후 암호를 찾아야 한다.
  • 접속하지 않고 외부에서 cat으로 찾아봤자 존재하지 않는다.

암호입력후 플러그인 설치 및 계정 설정 후 Jenkins 접속

노드관리 접속 후 아래 보이는 것처럼 Configure Clouds 클릭

  • node의 이름과 통신할 docker의 host URI를 설정

  • host URI는 docker container생성할 때 설정해주었다.

  • server credentials 클릭 후 x.509설정

  • 아래와 같이 총 세개의 키를 등록해주어야 한다.

docker container exec jenkins-docker cat /certs/client/key.pem
# client key 복사 후 등록
docker container exec jenkins-docker cat /certs/client/cert.pem
# client certificate 복사 후 등록
docker container exec jenkins-docker cat /certs/client/ca.pem
# server ca certificate 복사 후 등록
  • 해당 키들은 docker container 생성 시에 certs디렉터리에 생성 및 보관해 두었다.

  • enabled 활성화
  • Container Cap : 최대 몇개의 노드를 동시에 생성할 건지

Docker에서 Jenkins 활용

  • 노드 설정이 완료되었다면 테스트를 해보자

jenkins 테스트코드

  • 해당 테스트코드는 jenkins직원이 만들어놓은 것이다
  • jenkins에 새로운 프로젝트(pipeline)으로 생성 후 pipe line script from SCM선택
  • 위의 git 주소 등록 및 main으로 전환한다

1 . jenkinsfile-1

  • jenkinsfile -> jenkinsfile-1
pipeline {
  agent {
    docker { image 'node:16-alpine' }
  }
  stages {
    stage('Test') {
      steps {
        sh 'node --version'
      }
    }
  }
}
#jenkinsfile-1의 내용 

  • pipeline을 빌드해보면 설정된 docker image로 컨테이너(=노드)를 생성 후 해당 노드에 명령을 수행한 것이다.
  • 작업을 수행한뒤 노드는 다시 삭제된다

2 . jenkinsfile2

pipeline {
  agent none
  stages {
    stage('Back-end') {
      agent {
        docker { image 'maven:3.8.1-adoptopenjdk-11' }
      }
      steps {
        sh 'mvn --version'
      }
    }
    stage('Front-end') {
      agent {
        docker { image 'node:16-alpine' }
      }
      steps {
        sh 'node --version'
      }
    }
  }
}

  • maven이미지로 컨테이너(=노드)가 생성되고 작업 후에 삭제된다
  • 이후 node이미지로 컨테이너가 생성되고 작업 후에 삭제된다

docker , 쿠버네티스를 사용하는 가장 큰 장점

  • 이전에 가상머신을 이용해서 linux에서 jenkins를 사용했을 때는 노드를 생성하기 위해 VM(EC2)를 생성했었다. 컨트롤러가 작업이 있을 때 해당 노드에 작업을 시켜 수행하였다.
  • 하지만 VM의 특성상 노드가 일을 하든 안하든 항상 띄워놔야 하기 때문에 리소스와 요금의 낭비가 심하다.
  • dind 구조로 도커안에 jenkins를 설치한다면 비록 설치과정은 매우 어렵고 복잡하지만 jenkins가 작업을 수행하기 위한 노드를 VM이 아닌 컨테이너로 생성한다.
    -> 도커안에 jenkins가 도커 명령을 사용할 수 있도록 설치과정에서 설정하였는데, 이것은 jenkins가 컨테이너를 생성하기 위해서 필수적이다.
  • 위의 1번 , 2번 실습에서 알 수 있다시피, Jenkins컨트롤러가 작업이 생긴다면 일시적으로 컨테이너(=노드)를 띄워서 작업을 수행 후 종료시킨다.
    -> 컨테이너 특성 상 VM과 다르게 항상 띄워놓고 있지 않아도 된다
    -> 따라서 불필요하게 노드 리소스를 띄워놓을 필요가 없다

📒 결론 : 도커나 쿠버네티스는 노드를 동적으로 생성해서 필요할때 생성, 필요하지 않으면 사용하지 않을 수 있다. 이것이 복잡하지만 굳이 도커나 쿠버네티스를 이용해 컨테이너로 작업을 하는 이유이다.

Jenkins로 docker image생성

  • 이미지 생성 후 dockerhub에 push

1 . dockerhub에 로그인하기 위한 인증

  • dockerhub에 account settings 클릭
  • security클릭
  • read&write 권한으로 토큰 생성
  • jenkins에서 jenkins관리 접속
  • Manage Credentials클릭 후 Add credentials에서 아래와 같이 계정명과 위에서 생성한 토큰 입력
  • 젠킨스 내장문서(pipeline-syntax)의 snippet generator에서 위에서 생성한 credential을 이용하여 script생성

2 . Jenkinsfile

pipeline{
    agent none

    triggers{
    pollSCM '* * * * *'
    }

    parameters {
    string name: 'IMAGE_NAME' , defaultValue: 'hello-world'
    string name: 'IMAGE_REGISTRY_ACCOUNT', defaultValue: 'suhwan11' 
    }
    # 계정명과 이미지명 변수화 

    stages {
        stage('SCM Checkout'){
        agent any
            steps{
                git branch: 'main', url: 'https://github.com/suhwan12/abc.git'
            }
        }
        stage('maven build project'){
        agent{ docker { image 'maven:3-openjdk-8' }} 
            steps{
                sh 'mvn clean package -DskipTests=true' # test는 따로하기 위해 스킵한다
            }
        }
        stage('Test Maven Project'){  
            agent { docker { image 'maven:3-openjdk-8'} }
            steps{
              sh 'mvn test'
            }
        } # 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('publishing 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}"
            # 1번에서 생성한 script 추가(= 로그인) 및 로그인후 수행할 명령어 작성 
            }
        }
    }

    }
}
  • 빌드와 테스트는 같은 stage에서 하는 것보다 분리해서 하는 것이 좋다

3 . Dockerfile

FROM tomcat:9-jre8
COPY target/hello-world.war /usr/local/tomcat/webapps/

4 . 프로젝트 빌드

git add .
git commit -m 'Add jenkinsfile & dockerfile'
git push 
  • commit후 push한다음에 처음에는 수동으로 빌드하고 이후에 새로운 푸쉬가 들어오면 트리거에의해 자동으로 빌드
docker container exec jenkins-docker docker image ls

  • 새로운 이미지가 생성된 것을 확인

  • docker hub에도 생성된 이미지가 push된 것을 확인
docker container run -d --name myweb -p 80:8080 suhwan11:hello-world 
  • 이미지가 정상적으로 작동하는지 확인하기 위해 이미지로 컨테이너를 만들어본다
  • 포트포워딩은 이미 jenkins가 8080을 사용하기 때문에 80으로 해준다.
  • 브라우저에도 정상적으로 웹페이지가 뜨는 것을 확인

5 . 이미지 버전관리

  • 버전을 1.0.5로 수정한뒤 새로 commit,push를 하게 되면 새로운 이미지가 만들어지고 dockerhub에도 새로운 이미지가 올라오는 것을 알 수 있다.
  • 하지만 브라우저에 주소를 입력했을 때 버전이 바뀌지 않는 것을 알 수 있다.
    • docker, kubernetes는 기본적으로 이미지를 풀링할때 풀링 정책(missing, always, never)이 있다
    • default 정책(missing) : 로컬에 이미 이미지가 존재하면 풀링 x
    • 따라서 버전을 바꾼 새로운 latest이미지를 생성하면 기존의 이미지와 해쉬값은 다르더라도 이미지의 이름이 똑같기 때문에 run 을 할때 버전이 바뀐 새로운 이미지를 풀링하지 않는다
docker container run -d --name myweb --pull always -p 80:8080 suhwan11/hello-world
  • run을 할때 풀링 정책으로 always를 사용해면 해결되지만, 이것은 항상 새로운 이미지를 풀링하는 것이기 때문에 네트워크 트래픽 낭비가 심하다.

  • jenkinsfile에서 환경변수로 env.BUILD_NUMBER라는 전역변수를 추가한다.
  • 태그명에 빌드한 순서대로 버전이 붙는다
    -> 새로 commit,push를 할때마다 새롭게 빌드게 되어 생성되는 이미지가 구분이 된다


  • 태그명에 빌드 순서대로 넘버링이 된 이미지가 생성되는 것을 확인
  • docekrhub에도 이미지가 생성되는 것을 확인
docker container run -d --name myweb2 -p 80:8080 suhwan11/hello-world:60
  • 60번째 빌드순서로 태그가 붙은 이미지를 사용하여 컨테이너를 생성하면 브라우저에 수정된 버전으로 웹페이지가 뜨는 것을 볼 수 있다

📒 현업에서는 BUILD_NUMBER를 사용하지 않고 BUILD_TAG를 사용한 semantic versioning을 구현한다.

profile
성실하게 열심히!

0개의 댓글