이번 실습에서는 Docker도 사용해 보고 젠킨스도 사용해 볼 것인데

마지막엔 DockerHub, Github, Jenkins를 연동하고 Jenkins에서 CI/CD를 사용할 것이다.

Github Checkout → DockerBuild → Deploy to Kubernete

세 단계를 진행할 것이다.

Docker

EKS 환경 세팅을 다하고 나서 배스천 서버에서 docker 명령어를 사용해 본다.

docker images

$ docker pull ubuntu:20.04
	20.04: Pulling from library/ubuntu
	43cfb69dbb46: Pull complete
	Digest: sha256:71b82b8e734f5cd0b3533a16f40ca1271f28d87343972bb4cd6bd6c38f1bd38e
	Status: Downloaded newer image for ubuntu:20.04
	docker.io/library/ubuntu:20.04
$ docker images
	REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
	ubuntu       20.04     33985b2ba010   5 days ago   72.8MB

Dockerhub에 로그인하지 않았는데도 pull로 우분투 이미지를 받아왔다.

이는 퍼블릭 레포지토리에 접근했기 때문이다.

Untitled

마지막에 docker.io/library/ubuntu:20.04로 다운받은 레포지토리 주소가 나오길래 해당 주소로 들어가 봤다.

Untitled

이 레포지토리는 퍼블릭으로 공개되어 있어 아무나 접근 가능하고 태그를 붙이거나 레포지토리 명을 붙여서 이미지를 다운받을 수 있다.

$ docker pull library/ubuntu:22.04
	22.04: Pulling from library/ubuntu
	3c645031de29: Pull complete
	Digest: sha256:1b8d8ff4777f36f19bfe73ee4df61e3a0b789caeff29caa019539ec7c9a57f95
	Status: Downloaded newer image for ubuntu:22.04
	docker.io/library/ubuntu:22.04
$ docker images
	REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
	ubuntu       22.04     7af9ba4f0a47   5 days ago   77.9MB
	ubuntu       20.04     33985b2ba010   5 days ago   72.8MB

그래서 library/ubuntu:22.04로 이미지 다운로드가 가능하다는 것을 확인할 수 있었다.

Dockerizing

Dockerfile로 ubuntu라는 베이스 이미지에 올린 나만의 도커이미지를 하나 생성해 보겠다.

FROM ubuntu:20.04
ENV TZ=Asia/Seoul VERSION=1.0.0 NICK=6ain
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
    sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
    sed -i 's/security.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && \
    apt-get update && apt-get install -y apache2 figlet && \
    echo "$NICK Web Server $VERSION<br>" > /var/www/html/index.html && \
    echo "<pre>" >> /var/www/html/index.html && \
    figlet AEWS Study >> /var/www/html/index.html && \
    echo "</pre>" >> /var/www/html/index.html
EXPOSE 80
CMD ["usr/sbin/apache2ctl", "-DFOREGROUND"]

Dockerfile를 만들었으면 이미지를 빌드하고

$ docker build -t myweb:v1.0.0
$ docker images
	REPOSITORY   TAG       IMAGE ID       CREATED                  SIZE
	myweb        v1.0.0    7e3e886152db   Less than a second ago   237MB
	ubuntu       20.04     33985b2ba010   5 days ago               72.8MB

FROM 절에서 ubuntu를 pull해 오기 때문에 ubuntu도 로컬에 같이 다운로드됐다.

그리고 Dockerfile로 실행된 이미지의 히스토리를 확인해 볼 수 있다.

$ docker image history myweb:v1.0.0
	IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
	7e3e886152db   1 second ago     /bin/sh -c #(nop)  CMD ["usr/sbin/apache2ctl…   0B
	221747eb527f   1 second ago     /bin/sh -c #(nop)  EXPOSE 80                    0B
	69d556e4763e   2 seconds ago    /bin/sh -c ln -snf /usr/share/zoneinfo/$TZ /…   165MB
	351f7324a915   21 seconds ago   /bin/sh -c #(nop)  ENV TZ=Asia/Seoul VERSION…   0B
	33985b2ba010   5 days ago       /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
	<missing>      5 days ago       /bin/sh -c #(nop) ADD file:ea2128e23dce01625…   72.8MB
	<missing>      5 days ago       /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B
	<missing>      5 days ago       /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B
	<missing>      5 days ago       /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B
	<missing>      5 days ago       /bin/sh -c #(nop)  ARG RELEASE                  0

이제 이 빌드된 이미지를 컨테이너로 실행해 볼것이다.

$ docker run -d -p 80:80 --rm --name myweb myweb:v1.0.0
	42d83a1d9b229c8d4bc4677346b5fa688da228beef200836fa7cd3ce6e6580dc
$ docker ps
	CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                               NAMES
	42d83a1d9b22   myweb:v1.0.0   "usr/sbin/apache2ctl…"   46 seconds ago   Up 46 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   myweb

Untitled 2

생성할 때 --rm 옵션을 붙였기 때문에 docker stop [컨테이너ID] 하면 컨테이너는 바로 삭제된다!

또는 docker rm -f myweb로 삭제할 수 있다.

참고로 -f 옵션은 프로세스를 강제 종료하는 SIGKILL을 사용한다.

Untitled 3

Dockerhub

우선 이미지 저장소로 Dockerhub를 사용하기 위해 가입부터 하고 진행하자!

DHUB에 내 Dockerhub 아이디를 환경 변수로 지정하고 태그를 붙인다.

$ DHUB=6ain
$ docker tag myweb:v1.0.0 $DHUB/myweb:v1.0.0

그럼 태그 붙인 이미지가 하나 생성된다.

Untitled 4

이제 내가 가입한 계정으로 Dockerhub에 로그인하자!

Untitled 5

로그인하면 내 정보가 ~/docker/config.json에 저장되고

Untitled 6

로그아웃하면 바로 삭제된다.

Untitled 7

그리고 이미지를 푸시했을 때, 아까 퍼블릭 레포지토리와는 다르게 로그인을 안하면 내 레포지토리에 푸시는 거부당한다. 내 레포는 프라이빗이라 아무나 접근못하기 때문!

Untitled 8

다시 로그인하고 푸시해 보면

Untitled 9

성공적으로 레포지토리에 올라가 있다!

Untitled 10

그리고 만약 로컬에 이미지가 없다면 해당 레포지토리에서 새롭게 이미지를 다운받아 컨테이너를 생성할 수도 있다!

Untitled 11

Jenkins

젠킨스는 CICD를 제공하는 오픈소스 자동화 도구이다.

다양한 플러그인을 제공하여 사용할 수 있고 파이프라인을 통한 소프트웨어 빌드, 테스트 및 배포를 지원한다.

여러 플러그인을 제공하니 편리하게 사용할 수 있는데, 예를 들어 Git과 연동하고 특정 조건을 걸면 레포지토리에 push 또는 merge할 때마다 젠킨스 파이프라인을 구동시킬 수 있다. 또는 크론을 이용해서 특정 주기마다 파이프라인을 구동시킬 수 있다.

설치과정

이제 젠킨스를 설치하겠다.

실습 편리를 위해 root 계정으로 전환한다. 그리고 젠킨스 설치하기 위해 자바 17버전을 설치한다. 젠킨스는 자바로 만들어진 오픈소스이기 때문!

sudo yum install fontconfig java-17-amazon-corretto -y
JAVA_HOME=/usr/lib/jvm/java-17-amazon-corretto.x86_64

젠킨스를 설치하고 시작한다.

sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
sudo yum upgrade
sudo yum install jenkins -y
sudo systemctl daemon-reload
sudo systemctl enable jenkins && sudo systemctl start jenkins
sudo systemctl status jenkins

Untitled 12

설치하고 해당 주소로 들어가면 Administrator password를 입력하라고 뜨는데 이는 /var/lib/jenkins/secrets/initialAdminPassword를 확인해 보면 알 수 있다.

Untitled

이제 플러그인을 설치할 것이다.

Untitled

근데 젠킨스를 처음 설치할 땐 바로 플러그인 설치가 잘되더니, 며칠 뒤에 두 번째 설치 때는 잘 진행되지 않았다.

서버에 접속해 보면 에러 메세지가 떠있다.

+) 실패한 플러그인이 보이면 Jenkins 관리 > Plugins에서 바로 다시 설치해 줘야 한다. 나는 파이프라인 실습을 하다가 자꾸 제대로 파이프라인 생성을 못하고 빈 파이프라인만 생성되길래 보니까 플러그인이 없어서 발생한 일이었다ㅠ

Untitled 15

젠킨스를 설치한 EC2 보안그룹에서 HTTP 통신을 허용하지 않아서 발생한 이슈라고 하는데, 왜 처음에는 설치가 잘됐던건지 모르겠다,..

보안그룹을 허용해 주고 젠킨스 주소 뒤에 /safeRestart 붙여서 접속하면 서버에서 재부팅을 실행한다. 놀랍게도 정말 빠른 설치가 완료되었다.. 실습 못하는줄 알았는데 같은 스터디를 진행하는 스터디원분의 블로그 덕분에 살았다!

[AEWS] 7-1. Amazon EKS - CI/CD (Jenkins)

이유가 궁금해서 플러그인 설치를 어떤 방식으로 진행하는지 찾아 보았다.

우선 플러그인은 수동으로 파일을 넣어줘서 설치하거나 update site에서 설치할 수 있다.

/manage/pluginManager/advanced 에 들어가면 확인할 수 있다.

Untitled 16

그리고 좀 더 찾아 보면 REST API POST 방식으로 진행한다는 점을 알 수 있었다.

/manage/pluginManager/api를 참조하였다.

Untitled 17

  1. 사용자가 플러그인 설치를 요청하면
  2. 브라우저에서 /installNecessaryPlugins에 POST 요청으로 XML 형식의 데이터를 전송한다. 참고로 /prevalidateConfig에 POST 요청을 보내면 젠킨스 서버에서 필요한 플러그인 목록을 JSON 배열로 응답하여, 플러그인 목록을 미리 확인하는 유효성 검사를 진행할 수 있다.
  3. 젠킨스에서는 XML 데이터를 분석하고 필요한 플러그인이 있는지 확인한다.
  4. 설치할 플러그인이 있다면 https://updates.jenkins.io/update-center.json에서 플러그인 파일을 다운받게 된다.
  5. 플러그인 파일을 다운로드한 후, 해당 플로그인을 서버에서 설치/업데이트한다.
  6. 완료되면 update center API를 이용해 진행 상황을 체크하고 경우에 따라 restart API를 호출하여 젠킨스를 재시작하고 변경사항을 적용한다.

이 과정 속에서 2번에 브라우저에서 젠킨스 서버에 POST 요청할 때 보안그룹에 의해 진행이 안된걸로 이해하였다.

기본 사용

우선 Jenkins 관리 → Tools에서 JDK 관련 설정을 진행한다.

Untitled

저장하고 이제 Freestyle project를 생성해 본다.

Configuration에서 Build Steps를 누르면 Execute shell을 실행할 수 있다.

Untitled

테스트로 echo "Aws Workshop Study”를 저장하고 지금 빌드를 눌러보면

Console Output에서 잘 출력된 점을 확인할 수 있다!

Untitled

Build Steps을 수정해서 명령어 몇 줄 더 추가해 보았다

Untitled

빠짐없이 모든 명령어를 다 실행하였다!

Untitled

하지만 만약 실행 도중에 실행할 수 없는 명령어가 있다면 어떻게 될까?

Untitled

쉘스크립트 특성 상 이전 명령어가 실패하면 명령어 간의 종속성 때문에 다음 명령어를 수행하지 않는다.

Untitled

참고로 만약 여러 Step이 있을 때, 중간에 실패하는 Step이 있다면 다음 Step은 수행하지 않는다. 이는 젠킨스 파이프라인도 종속적인 특징을 띄고 있기 때문이다.

  • 이전 Step에서 성공했을 때

    Untitled 25

  • 이전 Step에서 실패했을 때

    Untitled 26

Docker 사용

jenkins 유저가 docker를 사용하려면 권한을 설정해 줘야 한다.

먼저 jenkins 유저가 bash를 사용할 수 있게 변경해 줘야 한다. 기본은 /bin/false로 되어 있다.

usermod -s /bin/bash jenkins로 변경해 주자!

Untitled 27

jenkins 유저로 전환하자!

su - jenkins

-bash-4.2$ whoami
	jenkins
-bash-4.2$ pwd
	/var/lib/jenkins

그리고 docker를 사용해 보면?

Untitled 28

permission denied로 사용할 수 없다.

다시 권한 설정을 해 주면 정상적으로 docker 사용이 가능해 진다!

chmod 666 /var/run/docker.sock
usermod -aG docker jenkins

docker login 후 Dockerfile 하나 생성해서 젠킨스 테스트를 진행하자! (위 Dockerfile 참고)

역시 성공하였다 역시나!

Untitled 29

Git

https://github.com/gasida/aews-cicd

가시다님이 제공해 주신 샘플 레포지토리를 포크해서 진행한다.

프로젝트 하나를 생성하고 Configure를 수정한다.

  1. General > 매개변수

    컨테이너 이미지 버전과 내 깃헙 계정명을 변수로 지정한다.

    Untitled

  2. 소스 코드 관리

    레포지토리 main 브랜치의 1 디렉토리를 대상으로 한다.

    Untitled

  3. 빌드 유발

    조건을 매 분마다로 설정해서 Git의 변경사항을 매 분마다 감시한다.

    Untitled

  4. Build Steps

    이제 조건에 만족할 때, 기존 Dockerfile을 삭제하고 다시 다운받아서 컨테이너를 생성한다.

    Untitled

다 만들었으면 이제 Github에 해당 경로로 들어가서 수정해 본다.

Untitled 34

빌드 성공!

Untitled

pipeline

젠킨스 파이프라인이란 전체 빌드 프로세스를 정의할 수 있는 플러그인 스크립트 모음이다.

Untitled 36

Pipeline

파이프라인 구성 형태는 Pipeline script, Pipeline script from SCM, Blue Ocean 기반 세 가지가 있다.

나는 Pipeline script으로 선언형 파이프라인으로 사용해 보았다.

일반적인 방식으로 쉘스크립트를 직접 생성하여 빌드하는 방식인데 아래와 같은 형식을 가진다.

pipeline {
    agent any     # Execute this Pipeline or any of its stages, on any available agent.
    stages {
        stage('Build') {   # Defines the "Build" stage.
            steps {
                //         # Perform some steps related to the "Build" stage.
            }
        }
        stage('Test') { 
            steps {
                // 
            }
        }
        stage('Deploy') { 
            steps {
                // 
            }
        }
    }
}
  • 간단하게 스테이지 두 개 만들어서 테스트해 보았다.

      pipeline {
          agent any
        
          stages {
              stage('Hello') {
                  steps {
                      echo 'Hello World'
                      sh 'java -version'
                  }
              }
              stage('Deploy') {
                  steps {
                      echo "Deployed successfully!";
                  }
              }
          }
      }
        
    

    Untitled 37

  • 파라미터도 사용해 보았다.

      pipeline {
          agent any
          parameters {
              string(name: 'PERSON', defaultValue: '6ain', description: 'Who are you?')
              choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
          }
          stages {
              stage('Parameter') {
                  steps {
                      echo "Hello ${params.PERSON}"
                      echo "Choice: ${params.CHOICE}"
                  }
              }
          }
      }
    

    파라미터와 함께 빌드를 눌러서 CHOICE 값을 하나 선택해서 빌드가 가능하다.

    Untitled 38

    Untitled 39

Jenkins with Kubernetes

젠킨스에서 쿠버네티스를 구동시키려면?

배스천에서 api server에게 request를 날릴 수 있는 환경을 만든 것처럼 젠킨스도 api server에게 request를 날릴 수 있는 권한을 주면 된다.

바로 kubeconfig를 만들어 주자!

jenkins 유저로 로그인한 상태에서 ~/.kube 디렉토리를 만들어 주고

다시 root 계정으로 로그인해서 파일을 복사해 주고 권한을 설정해 준다.

cp ~/.kube/config /var/lib/jenkins/.kube/config
chown jenkins:jenkins /var/lib/jenkins/.kube/config

이제 젠킨스도 kubectl 사용이 가능하다.

Untitled 40

EC2에 파드를 생성할 때는 aws configure로 자격증명을 해 줘야 한다.

마지막으로 스크립트를 작성하고

pipeline {
    agent any

    tools {
        jdk 'jdk-17'
    }

    environment {
        DOCKERHUB_USERNAME = '6ain'
        GITHUB_URL = 'https://github.com/gain-yoo/aews-cicd.git'
        DIR_NUM = '3'
    }

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

                // 컨테이너 빌드 및 업로드
                sh "docker build -t ${DOCKERHUB_USERNAME}/myweb:v1.0.0 ./${DIR_NUM}"
                sh "docker push ${DOCKERHUB_USERNAME}/myweb:v1.0.0"
            }
        }

        stage('K8S Deploy') {
            steps {
                sh "kubectl apply -f ./${DIR_NUM}/deploy/deployment-svc.yaml"
            }
        }
    }
}

Git에서 Dockerfile과 deployment.yaml을 가져와서

Untitled 41

컨테이너를 빌드하고 이미지를 만들어서 deployment.yaml로 파드를 생성할 것이다!

Untitled

Untitled 43

빌드도 성공하고 파드도 생성되었다!

나도 컨테이너 전환하는 업무를 할 땐 젠킨스를 사용했는데 그 땐 고객사에서 젠킨스를 고객사의 DevOps 도구로 통합시켜서 SaaS화하여 UI로 잘구현해놨었다.

그래서 구축하고 스크립트로 작성해서 오리지널 그대로 사용하는 것은 처음 진행해 보았다!

역시 기본 날 것을(?) 건드려 봐야 한다!

Categories:

Updated:

Leave a comment