인프라/Jenkins 2020. 3. 27. 17:34

 

오늘 다루어볼 포스팅은 Git과 Jenkins를 연동한 이후에 Jenkins Mulibranch pipeline으로 springboot web project를 docker image로 빌드 후 push하는 것까지 다루어볼 것이다. 이전까지는 오늘 다루어볼 포스팅의 연습 및 준비단계였고 본격적으로 CI/CD에 대해 다루어본다. 물론 오늘 다루어볼 포스팅 내용이 실무에서 그대로 활용하기 애매할 수는 있지만 포스팅을 이어나가면서 좀 더 보완해나갈 것이다.

 

1. Git & Jenkins 연동 후 Multibranch pipeline 작성

 

 

Jenkins - CentOS에 docker 환경으로 Jenkins 설치(Jenkins&GitHub CI)

오늘 다루어볼 내용은 CI의 핵심 젠킨스(Jenkins)를 다루어볼 것이다. 사실 이번 포스팅을 시작으로 젠킨스(Jenkins) 관련 몇개의 포스팅이 더 있을 듯하다. 오늘은 간단히 CentOS환경에 docker로 Jenkins를 설치..

coding-start.tistory.com

 

 

Jenkins - Jenkins&GitHub을 이용하여 아주 간단한 MultiBranch pipeline 만들어보기

이전 포스팅에 이어 Jenkins&GitHub을 이용한 CI 실습을 다루어볼 것이다. 이번 포스팅에서는 본격적으로 실습을 진행하기 전에 아주 간단한 echo를 찍어주는 pipeline 하나를 만들어 볼 것이다. 사실 이것을 설..

coding-start.tistory.com

 

위의 내용에 Git & Jenkins 및 간단한 Multibranch pipeline을 연동하는 내용이 있다. 위의 링크를 참조하고, 위에 없는 내용중 보완할 내용만 추가로 다룬다. 젠킨스와 깃의 연동은 크게 다르지 않다. 하지만 Multibranch pipeline 설정이 조금 보완되었다. 

 

1.1. Jenkins Multibranch pipeline 작성

 

General - Display Name과 Description을 작성해준다. (실행에 크게 영향 x)

 

*Branch Sources - 가장 핵심이 되는 configuration 영역이다. Github Credentials와 Repository HTTPS URL을 작성해준다. Credetials는 Jenkins Credentials에서 미리 만들어준다.(github의 인증정보 username/password로 작성하면 된다.) 그리고 깃헙의 https 주소를 넣어준다.

 

 

다음은 push & pull request event를 어떤 branch에 대해 인식시킬지에 대한 설정이다. 그리고 clean after checkout & clean before checkout을 넣어준다.

 

 

master & develop branch에 대해 push&pull request를 인식시키도록 하였다. 추후에는 일반 PR브랜치에 대해 인식시키면 feature branch등도 push&pull request 이벤트로 반응하도록 할 수 있을 것이다.

 

Clean after checkout & Clean before checkout은 .gitignore에 지정된 것을 포함하여 추적되지 않은 모든 파일과 디렉토리를 삭제하여 모든 체크 아웃 전후에 작업 공간을 정리하는 옵션이다.(테스트 결과 등의 파일정리) 또한 소스파일은 업데이트 된 경우 최신 변경사항을 가져오게 된다. Wipe out repository & force clone이라는 비슷한 옵션도 있지만, 이 옵션은 전체 프로젝트 작업 공간을 정리하고 빌드 전에 프로젝트를 다시 한번 클론하기 때문에 시간이 오래 걸린다.(프로젝트가 1기가라면 빌드 할 때마다 1기가를 pull받는다.)

 

 

마지막으로 push&pull request event가 발생시키면 젠킨스는 프로젝트 root에 있는 Jenkinsfile을 기준으로 pipeline을 기동시킨다는 설정이다.

 

2. Dockerfile & Jenkinsfile 작성

Jenkinsfile을 작성하기 전에 docker image build를 위해 우리는 Jenkins을 dood(docker out of docker)로 기동시켜야한다. 만약 준비가 되어 있지 않다면 아래 링크를 참조하자.

 

 

Jenkins - Jenkins dood(docker out of docker)로 실행시켜 agent docker 사용하기

아마 이번 포스팅부터 Jenkins(젠킨스)에서 정말 쓸모 있는 부분을 다룰 것 같다. 젠킨스 파이프라인을 다루기 전에 Jenkins 안에서 dood(docker out of docker)로 docker agent를 실행하는 방법에 대해 다루어..

coding-start.tistory.com

 

그리고 간단히 springboot web project를 생성해준다. 우리는 springboot web을 docker image로 빌드할 것이다.

 

2.1. Dockerfile 작성

간단히 우리가 만든 springboot web image를 빌드하기 위한 Dockerfile을 작성하자.

 

FROM adoptopenjdk/openjdk13:alpine-jre
MAINTAINER levi <1223yys@naver.com>

COPY ./build/libs/jenkins_sample-0.0.1-SNAPSHOT.jar /app/app.jar

EXPOSE 8080
CMD ["java", "-jar", "/app/app.jar"]

 

큰 내용은 없다. openjdk13을 base image로 하여 우리가 빌드한 springboot jar파일을 카피하여 실행시키는 image다.

 

2.2. Jenkinsfile 작성

 

pipeline {
    agent none

    environment {
        SLACK_CHANNEL = '#jenkins_notification'

        REGISTRY = '1223yys/spring-web-jenkins'
        REGISTRYCREDENTIAL = '1223yys'
    }

    stages {
        stage('Start') {
            agent any
            steps {
                slackSend (channel: SLACK_CHANNEL, color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
            }
        }

        stage('Test') {
            agent {
                docker {
                    image 'adoptopenjdk/openjdk13:alpine'
                    alwaysPull true
                }
            }
            steps {
                sh './gradlew test --no-daemon'
            }
        }

        stage('Build') {
            agent {
                docker {
                    image 'adoptopenjdk/openjdk13:alpine'
                    alwaysPull true
                }
            }
            steps {
                sh './gradlew --no-daemon build -x test'
            }
            post {
                success {
                    stash includes: '**/build/**', name: 'build'
                }
            }
        }

        stage('Docker image build & push') {
            agent any
            steps {
                unstash 'build'
                //docker username/password credential
                sh 'docker login -u username -p password'
                sh 'docker build -t $REGISTRY:latest .'
            }
            post {
                success {
                    sh 'docker image ls | grep 1223yys'
                    sh 'docker push $REGISTRY:latest'
                }
            }
        }

        stage('Clean docker image') {
            agent any
            steps {
                sh 'docker rmi $REGISTRY'
            }
        }
    }

    post {
        success {
            slackSend (channel: SLACK_CHANNEL, color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        failure {
            slackSend (channel: SLACK_CHANNEL, color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}

 

파이프라인의 모든 문법을 다루어보지는 못하지만 위에 작성된 문법정도는 다루어볼 것이다. 우선 항상 pipeline {} 가 root에 작성되어야하고 항상 해당 블록내에 파이프라인 스크립트를 작성한다. 그리고 바로 밑에 agent none이 보이는데, pipeline 바로 밑에 agent가 있으면 이것은 글로벌한 agent가 되기 때문에 각 steps마다 다른 agent 사용이 필요할 때는 사용하기 애매하기 때문에 none으로 지정하였다.

 

두번째는 environment {} 이다. 해당 설정에는 사용자 정의 환경설정 작성이 가능하지만 jenkins container 내부에서 사용하는 환경변수가 될수도 있다. 해당 설정은 중요하게 사용될 수 있다. 실제로 필자는 내부망 서버에 젠킨스를 올려 외부 파일을 받는 것이 되지 않아 environment에 proxy설정을 환경변수로 넣어놓아서 외부 파일을 다운로드 받을 수 있게 세팅하였다.

 

세번째는 slack에 파이프라인의 시작을 알림보내는 스크립트이다. 슬랙 알림에 대한 것은 아래 링크를 참고하자. agent any는 현재 젠킨스 인스턴스 중에 하나를 사용하겠다라는 설정이다.

 

 

Jenkins - Jenkins pipeline Slack notification(젠킨스 빌드 슬랙 알림)

오늘 다루어볼 포스팅은 간단하게 젠킨스의 빌드 시작과 성공/실패에 대한 알림을 슬랙으로 받는 방법이다. 간단해서 아주 짧은 포스팅이 될 것 같다. 1. Slack Jenkins CI app install 예제는 이미 슬랙이 설치..

coding-start.tistory.com

 

네번째는 Test stage이다. git에서 checkout 받은 파일중 gradlew를 이용해 테스트 하기 위해서 agent를 openjdk13 base docker image에서 실행하겠다라는 스크립트이다. agent를 docker로 설정하고 사용할 이미지와 image pull 전략을 넣었다. 그리고 해당 docker image(openjdk13) 환경에서 실행할 스크립트를 "./gradlew test --no-daemon"를 작성했다. 참고로 docker agent로 실행시키기 때문에 이 환경은 특정 이미지 컨테이너의 환경이기 때문에 docker 명령이 먹지 않는다. 그 이유는 openjdk container에는 도커 엔진이 없기 때문에 ! 잘 유의하자.

 

 

다섯번째는 Build stage이다. 네번째 테스트 스테이지를 무사히 통과하였다면 이 단계로 넘어온다. 위 테스트 스테이지와 동일한 base 이미지로 build를 수행한다. 하지만 하나 추가된 설정이 있다.

 

post {
   success {
       stash includes: '**/build/**', name: 'build'
   }
}

 

우리는 openjdk13을 볼륨마운트하는 설정을 어디에도 하지 않았고, 해당 이미지는 실행이 끝나면 docker container를 제거시킨다. 그렇기때문에 build된 jar파일이 없어질 것이다. 그래서 위와 같이 특정 파일을 stash(임시 저장)하여 필요할때 꺼내쓰기 위한 설정을 넣어주었다.

 

 

여섯번째는 도커 이미지 build 및 push 스테이지이다. 눈에 띄는 설정이 unstash 'build'이다. 도커 이미지를 빌드하기 위해 jar 파일이 필요하기 때문에 임시저장한 빌드 결과물을 꺼내고 있다. 그리고 해당 파일을 바탕으로 Dockerfile을 사용해 이미지를 빌드한다. agent any로 넣어줬기때문에 우리는 jenkins container로 빌드 잡을 수행한다. 이렇기 때문에 우리는 docker 명령 사용이 가능하다.

 

stage('Docker image build & push') {
    agent any
    steps {
        unstash 'build'
        sh 'ls -al'
        sh 'docker login -u username -p password'
        sh 'docker build -t $REGISTRY:latest .'
    }
    post {
        success {
            sh 'docker image ls | grep 1223yys'
            sh 'docker push $REGISTRY:latest'
        }
    }
}

 

 

마지막 스테이지는 빌드한 이미지를 clean하는 스크립트이다. 이미지를 빌드하면 결국 로컬 디스크에 저장되므로 쌓이기 시작하면 어느샌가 디스크를 많이 먹고 있을 것이다. 그렇기 때문에 매 빌드마다 clean해준다.

 

stage('Clean docker image') {
    agent any
    steps {
        sh 'docker rmi $REGISTRY'
    }
}

 

 

slack에 빌드 성공 메시지가 갔는지 확인해보자.

 

 

마지막으로 dockerhub에 우리가 빌드한 이미지가 잘 올라갔는지 확인하자 !

 

 

오 ! 잘올라갔다 ㅎㅎ 여기까지 간단히 Jenkins Multibranch pipeline을 다루어보았다. 사실 필자는 내부망 장비를 할당받아서 실습했기 때문에 굉장히 삽질을 많이 했다.(외부에서 파일 다운로드 이슈 등) 아마 퍼플릭 존이라면 큰 이슈없이 실습이 가능할 것이다. 혹시나 내부망에서 실습을 진행하다 잘안되는 부분이 있다면 댓글을 남겨주길 바란다.

 

실습한 코드는 아래 깃헙링크를 참고하자.

 

 

yoonyeoseong/jenkins_sample

Contribute to yoonyeoseong/jenkins_sample development by creating an account on GitHub.

github.com

 

posted by 여성게
:
인프라/Jenkins 2020. 3. 26. 00:11

 

아마 이번 포스팅부터 Jenkins(젠킨스)에서 정말 쓸모 있는 부분을 다룰 것 같다. 젠킨스 파이프라인을 다루기 전에 Jenkins 안에서 dood(docker out of docker)로 docker agent를 실행하는 방법에 대해 다루어 볼 것이다. 실제로 많이 사용하는 유즈케이스이기도 하다. 그 이유는 필요한 라이브러리를 직접 설치하거나 하지 않고 Jenkins pipeline 스크립트 안에서 docker agent를 이용해 설치하면 별도로 의존성있는 라이브러리를 직접 설치하지 않아도 되고, 또한 Jenkins에서 Docker image를 빌드할때도 필요하다. 즉, 요즘 같이 도커가 대중화되있는 시대에는 거의 필수이지 않을까 싶다.(더 좋은 방법이 있다면 댓글로 알려주시면 감사하겠다.)

 

우리가 구성해볼 것은 docker 안에 docker를 띄우는 것이긴 하지만 "docker in docker" 방식처럼 독립적으로 띄우지는 않을 것이고, Host docker의 docker.sock을 공유해서 docker out of docker방식으로 띄워볼 것이다. docker in docker라는 것도 존재하지만 권장하는 방법이 아니기 때문에 우리는 docker out of docker로 실습을 진행해볼 것이다.

 

그렇다면 왜 docker in docker가 권장하지 않는 방법일까? 이유는 아래와 같다.

 

Docker in Docker(DinD)는 도커 안에 컨테이너 내부의 격리된 Docker 데몬을 실행하는 작업을 의미한다. 
즉, 도커데몬이 2개가 뜨는 것이다. CI 측면에서 접근하자면 Task를 수행하는 Agent가 Docker Client와
Docker Daemon 역할까지 하게되어 도커 명령을 수행하는데 문제가 없어진다. 하지만 아주 큰 단점이 존재한다.

호스트 도커 컨테이너가 privilieged mode로 실행되어야 한다.

> docker run --privileged --name dind -d docker:1.8-dind

privilieged 플래그를 사용한다면 호스트 컨테이너가 호스트 머신에서 할 수 있는 거의 모든 작업을 할 수 있게 된다.
이는 컨테이너를 실행하는데 큰 보안 위험을 초래할 수 있다.

출저 : https://aidanbae.github.io/code/docker/dinddood/

그렇다고, dood가 아예 단점이 없는 것은 아니다. 그 이유는 직접 띄워보면 알 수 있는 것이 jenkins container 내부에서 docker ps 명령을 날려보면 자기자신의 container가 떠있는 것이 목록에 보인다. 그 말은? 외부 호스트 도커에 떠있는 container에 접근이 가능하게 될 수도 있다는 것이다. 또한 docker.sock을 공유하기 위해 그만큼의 권한을 docker container 에게 제공해야한다. 조금씩 장단점이 있는 방법들인 것 같으니, 안전하게 사용하는 쪽이 좋은 방향인것 같다.

 

우선 우리는 이전 포스팅에서 도커 젠킨스 공식 이미지를 사용했었는데, 조금 변경할 필요가 생긴다. 그 이유는 공식 젠킨스 이미지 안에는 도커 엔진이 설치되어 있지 않기때문에 우리는 도커엔진이 깔려있는 젠킨스 이미지가 필요하기 때문에 커스텀하게 이미지를 다시 만들어 볼것이다.

 

1. jenkins-dood image 만들기

바로 실습에 들어간다. 우선 도커 엔진을 다운로드 받기 위한 스크립트를 파일로 작성하였다.

 

> docker_install.sh

#!/bin/sh
apt-get update && \
apt-get -y install apt-transport-https \
     ca-certificates \
     curl \
     gnupg2 \
     zip \
     unzip \
     software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
   $(lsb_release -cs) \
   stable" && \
apt-get update && \
apt-get -y install docker-ce

 

그리고 다음으로는 도커 이미지를 빌드하기 위한 Dockerfile이다.

 

#공식 젠킨스 이미지를 베이스로 한다.
FROM jenkins/jenkins:lts

#root 계정으로 변경(for docker install)
USER root

#DIND(docker in docker)를 위해 docker 안에서 docker를 설치
COPY docker_install.sh /docker_install.sh
RUN chmod +x /docker_install.sh
RUN /docker_install.sh

RUN usermod -aG docker jenkins
USER jenkins

 

도커 이미지를 빌드할 준비가 되었다. 이제 도커 이미지를 빌드해보자. 

 

docker build -t 1223yys/jenkins-dind:latest .
docker push 1223yys/jenkins-dind

 

여기까지 크게 문제없이 따라왔다면 도커허브에 이미지가 push 되어 있을 것이다. 

 

 

그 다음은 Jenkins를 띄울 서버에 방금 만든 이미지로 Jenkins를 띄워보자.

 

docker run -d --name jenkins -p 8080:8080 -p 50000:50000 \
        -v /home/deploy/jenkins_v:/var/jenkins_home \
        -v /var/run/docker.sock:/var/run/docker.sock \
        1223yys/jenkins-dind:latest

 

중요한 것은 우리가 처음 이야기했던 것처럼 Host의 docker.sock을 공유해서 사용할 것임으로 볼륨마운트가 필요하다. 이제 젠킨스 컨테이너에 들어가서 도커엔진이 잘 떠있는지 확인하자.

 

> docker exec -it jenkins bash

jenkins@20ff53d54e94:/$ docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                                              NAMES
20ff53d54e94        1223yys/jenkins-dind:latest   "/sbin/tini -- /usr/…"   35 minutes ago      Up 35 minutes       0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp   jenkins

 

어? 나는 띄운 컨테이너가 없는데 떠있는 컨테이너가 하나 보인다 ! 그 이유는 앞에서도 간단히 이야기 한 것처럼 호스트 도커엔진과 공유하고 있기 때문이다 ! 우리가 실행 시킨 젠킨스 컨테이너가 보인다. 만약 "docker.sock 볼륨마운트"에 관해 permission denied가 발생한다면 아래와 같이 호스트 머신에서 docker.sock에 접근할 수 있도록 권한을 부여하자. 

 

> sudo chmod 666 /var/run/docker.sock

 

그리고 계속해서 docker 명령어를 sudo로 붙여서 사용하는 사람이 있다면 아래 명령어를 참고하자.

 

> sudo groupadd docker
> sudo usermod -aG docker deploy

 

이제 간단하게 젠킨스 파이프라인 스크립트(Jenkinsfile)를 작성해 도커엔진이 정말 잘 동작하는지 확인하자.

 

pipeline {
    agent none

    environment {
        SLACK_CHANNEL = '#jenkins_notification'
    }

    stages {
        stage('Start') {
            steps {
                slackSend (channel: SLACK_CHANNEL, color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
            }
        }

        stage('Build & Test') {
            agent {
                docker {
                    image 'gradle:6.0-jdk13'
                    alwaysPull true
                }
            }
            steps {
                sh '''
                    gradle -version
                '''
            }
            post {
                always {
                    echo 'Test and Build post process !'
                }
            }
        }

        stage('Deploy') {
            steps {
                echo 'jenkins sample deploy ! '
            }
        }

    }
    post {
        success {
            slackSend (channel: SLACK_CHANNEL, color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        failure {
            slackSend (channel: SLACK_CHANNEL, color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}

 

해당 파이프라인은 도커로 gradle을 내려받아 젠킨스 내부에 떠있는 도커의 gradle 컨테이너를 이용하여 gradle -version을 체크하는 간단한 스크립트이다. 블루오션으로 파이프라인 결과를 확인해보자 !

 

 

성공했다 ! 여기까지 Jenkins 컨테이너 안에 도커엔진을 설치하여 파이프라인에 docker를 사용할 수 있도록 구성하고 실습해보았다. 오늘까지는 실무에서 쓸만한 파이프라인 스크립트를 다루어나 하지는 않았지만, 다음 포스팅부터는 정말로 쓸만한 파이프라인을 구성해서 실습해볼 것이다. 만약 위 코드가 필요하다면 아래 깃헙주소를 참고하자.

 

 

 

yoonyeoseong/jenkins_sample

Contribute to yoonyeoseong/jenkins_sample development by creating an account on GitHub.

github.com

 

posted by 여성게
:
인프라/Jenkins 2020. 3. 22. 21:15

 

오늘 다루어볼 포스팅은 간단하게 젠킨스의 빌드 시작과 성공/실패에 대한 알림을 슬랙으로 받는 방법이다. 간단해서 아주 짧은 포스팅이 될 것 같다.

 

1. Slack Jenkins CI app install

 

예제는 이미 슬랙이 설치되어있고 계정이 있다는 가정에서 진행한다. 채널을 하나 생성해준 후 밑의 Apps 버튼을 누른다.

 

젠킨스(Jenkins)로 검색하여 다운로드 한다.

 

 

알림받을 채널을 입력한다.

 

 

진행하다보면 슬랙의 Team Subdomain과 인증 token을 받을 수 있다. 이러한 데이터를 이용하여 Jenkins에서 Slack과의 연동 설정을 할것이다.

 

 

젠킨스 설정에서 이제 슬랙과 연동을 할 것이다.

 

 

플러그인 관리에서 Slack Notification plugin을 install 해준다.

 

 

이제 Jenkins 설정 > 시스템 설정 > Slack에서 방금 받은 토큰과 Team Subdomain등을 입력할 것이다.

 

 

Test Connection을 클릭 후 Success가 뜬다면 완료 ! 

 

우리는 Multibranch pipeline에서 slack notification을 받을 것이므로, Jenkinsfile에 noti관련 스크립트를 추가할 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pipeline {
    agent none
 
    environment {
        SLACK_CHANNEL = '#test_notification'
    }
 
    stages {
        stage('Start') {
            steps {
                slackSend (channel: SLACK_CHANNEL, color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
            }
        }
 
        stage('example build') {
            steps {
                echo 'jenkins sample build ! '
            }
        }
    }
    post {
        success {
            slackSend (channel: SLACK_CHANNEL, color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        failure {
            slackSend (channel: SLACK_CHANNEL, color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}
cs

 

Start stage와 post step에 Slack noti script를 추가하였다. 한번 테스트해보자 !

 

 

와우 굿 ! 알림이 왔다. ㅎㅎ 이렇게 Jenkinsfile에 알림 스크립트를 추가하면 프로젝트마다 서로 다른 채널연결이 가능하고, 더 복잡한 조건에 대한 알림이 가능할 것이다 !

posted by 여성게
:
인프라/Jenkins 2020. 3. 21. 20:11

 

이전 포스팅에 이어 Jenkins&GitHub을 이용한 CI 실습을 다루어볼 것이다. 이번 포스팅에서는 본격적으로 실습을 진행하기 전에 아주 간단한 echo를 찍어주는 pipeline 하나를 만들어 볼 것이다. 사실 이것을 설명하는 이유는 멀티브랜치 파이프라인에 대해 조금 이해해보기 위함이다.

 

1. Multibranch Pipeline 생성

사실 우리가 개발할때, 버전관리 시스템에서 브랜치를 하나만 사용하여 개발하지 않는다. 각 회사마다의 브랜치 전략이 있겠지만 가장 대중적인 브랜치 전략은 아래와 같다.

 

  • Feature branch
  • Integration branch
  • Master branch(production branch)

이렇게 여러개의 브랜치를 가지고 개발을 진행하고 이러한 멀티 브랜치 전략에 대해 Jenkins를 이용하여 CI를 구현할때는 Multibranch Pipeline을 이용한다. 자세한 브랜치 전략은 구글링해서 참고하자 ㅎㅎ.

 

새로운 Item 메뉴를 클릭하면 다음과 같은 페이지가 보일 것이다.

 

 

item 이름을 넣고 Multibranch Pipeline을 생성해준다. 하나 설명하면 Multibranch Pipeline은 직접 스크립트를 Jenkins에서 작성하지 않고 소스코드가 있는 Github project root에 Jenkinsfile로 정의된다. Jenkinsfile은 쉽게 말하면 파이프라인 스크립트가 정의된 파일로 보면 된다.

 

Branch Sources 탭을 클릭하여 필요한 정보를 기입할 것이다. 지금은 자세한 설명보다는 파이프라인이 동작하는 것을 집중적으로 볼 것이기 때문에 정말 필요한 정보만 기입한다.

 

Repository HTTPS URL에 생성한 GitHub 레포지토리 주소를 기입한다. Discover branchesStrategyAll branches로 바꿔준다. Add 버튼을 클릭하고 Filter by name (with wildcards)를 추가한다. 그리고 Includemaster develop을 입력한다. 여기까지 작성하고 save해준다.

 

다음으로 간단하게 springboot project를 생성하고 해당 프로젝트 root에 Jenkinsfile을 생성해준다. 소스코드는 아래 깃헙 정보를 참고하자

 

 

yoonyeoseong/jenkins_sample

Contribute to yoonyeoseong/jenkins_sample development by creating an account on GitHub.

github.com

 

2. Jenkinsfile 작성

간단하게 Jenkinsfile을 작성해보자. 샘플은 위 깃헙에 있으니 내려받아도 무방하다.

 

pipeline {
    agent none

    stages {
        stage('example build') {
            steps {
                echo 'jenkins sample build ! ${AUTHOR}'
            }
        }
    }
    post {
        always {
            echo 'post process !'
        }
    }
}

 

여기까지 작성한 후 master branch를 기준으로 develop branch를 하나 딴 후에 간단히 수정 후 커밋&푸시 해보자 ! 우리는 이전 포스팅에서 "GitHub의 master와 develop 브랜치의 PR, push를 인식시킨다." 라는 설정을 넣었기 때문에 Jenkins pipeline이 돌기를 기대해야한다. 과연 파이프라인이 작동하였을까?

 

오?! 뭔가 돌았다?(필자는 테스트로 한번 더 돌려서 2개가 보인다.) #1을 클릭한 후에 정말 잘 돌았는지 Console Output을 보자 !

 

 

오호 우리가 정의한 파이프라인이 작동했다 ! 정말 신기방기하다. 정말 간단한 것이지만 여기까지 진행했다면 Jenkins&GitHub을 이용한 CI의 느낌을 어느정도 느꼈을 것이다. 그리고 조금더 감각있는 사람은 앞으로 어떠한 실습을 진행할지 예상도 될것이다. 여기까지 간단하게 멀티브랜치 파이프라인 실습을 마친다. 다음 포스팅부터는 본격적으로 실무적인 레벨의 실습을 다루어본다.

 

3. 지금까지 다룬것

 

  1. CentOS 환경에서 Docker로 Jenkins 설치
  2. Jenkins와 GitHub 연동
  3. develop branch로 수정한 후 커밋&푸시를 통해 간단한 멀티브랜치 파이프라인 동작확인
posted by 여성게
:
인프라/Jenkins 2020. 3. 21. 19:42

 

오늘 다루어볼 내용은 CI의 핵심 젠킨스(Jenkins)를 다루어볼 것이다. 사실 이번 포스팅을 시작으로 젠킨스(Jenkins) 관련 몇개의 포스팅이 더 있을 듯하다. 오늘은 간단히 CentOS환경에 docker로 Jenkins를 설치해볼 것이다. 바로 예제로 넘어간다.

 

필자는 미리 CentOS VM을 준비하였다.(사실 회사에서 장비를 맘껏 받아쓸 수 있어서 ㅎㅎ 실습용으로 생성했다.) 이번 포스팅은 CentOS가 준비되어 있다라는 가정이다.

 

1. CentOS에 Docker Engine 설치

첫번째로 Docker Engine을 설치해보자.

 

> sudo yum -y update
> sudo yum -y install docker docker-registry
> sudo systemctl start docker
> sudo docker ps
> sudo systemctl enable docker
> sudo systemctl status docker

 

위와 같은 순서로 도커엔진(docker engine)을 설치한다. 잘 설치가 되지 않거나, 혹은 다른 OS에서 실습을 진행중이라면 구글링해보자.

 

2. Docker Jenkins install

==========jenkins 공식이미지를 이용하여 jenkins 실행==========
> sudo docker run -d --name jenkins -p 8080:8080 -p 50000:50000 -v /home/deploy/jenkins_v:/var/jenkins_home jenkins/jenkins:lts

==========jenkins 설치 후 초기 비밀번호 조회==========
> sudo docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword

 

도커를 이용하여 젠킨스를 띄웠고 초기 비밀번호까지 획득했다면 8080포트로 접속해보자.

 

 

위와 같이 초기 비밀번호를 입력하는 창이 나온다. 아까 받아놓은 초기 비밀번호를 입력한다.

 

 

아마 사내인프라인 경우 위와 같이 offline이라고 뜰것이다. 젠킨스는 다양한 플러그인 설치 때문에 꼭 외부와 통신할 필요가 있다. 사내에 존재하는 proxy server를 이용하여 proxy 설정을 하자 !

 

 

Install suggested plugins를 눌러 필요한 플러그인을 모두 설치해준다. 플러그인이 모두 설치되었다면 이제 접속을 위한 인증정보를 작성해야한다.

 

 

모든 정보를 기입한 후 로그인해서 젠킨스 대시보드에 접속해보자 !

 

 

필자가 미리 만들어 놓은 Job을 제외하고는 거의 비슷한 화면이 떴을 것이다. 여기까지 젠킨스 설치가 완료되었다. 도커의 위대함을 다시 한번 느끼게 되는 실습이다. 만약 도커가 아니었다면 젠킨스 설치에 더 복잡한 프로세스가 있었을 것이다. 

 

3. Jenkins&Github 연동

이 포스팅의 최대 목표는 Jenkins를 이용한 CI 구성이다. 대부분 버전관리시스템은 git을 많이 이용하기 때문에 Jenkins&Github 연동을 통해 Jenkins pipeline을 구성할 것이다. 여기서 주의할 점은 Github에서 webhook을 걸기 위해 꼭 Jenkins는 외부에서 접근가능한 형태의 Domain 혹은 IP를 가지고 있어야한다. 만약 여기까지 문제없다면 바로 다음 실습을 진행한다.

 

첫번째로 Github 계정 정보를 넣어준다.

 

Credintials > System > Global credentials (unrestricted) click > Add Credentials 까지 들어온다.

 

kind는 Username with password로 하고 Username, Password 등 정보를 Github 로그인정보와 동일하게 기입해준다. 다음으로 Jenkins 설정 > 시스템 설정 메뉴로 들어와서 Github tab으로 이동한다.

 

 

고급 탭을 누른다. 고급탭을 누르면 Additional actions 탭이 생겼을 것이고 Manage additional Github actions를 눌러 Convert login and password to token을 눌러 우리가 넣었던 Github 인증 정보를 선택한다.

 

 

그런 다음 Create token credentials를 눌러 토큰을 생성해준다.

 

 

이제 Github에 접속하여 Generate된 토큰을 확인하자.

 

Settings > Developer settings > Personal access tokens 메뉴로 들어가면 우리가 Jenkins에서 생성한 token 정보가 보일 것이다.

 

 

다시 Jenkins설정 > 시스템 설정 > GitHub 탭으로 돌아가서 GitHub Server 정보를 입력하자. Name은 편한이름을 넣으면 되고, API URLhttps://api.github.com을 입력하자. 여기서 주의할 것은 혹시나 사내에서 GitHub 엔터프라이즈 버전을 사용한다면 API URL을 자체적으로 가지고 있으니 그것을 입력한다. 마지막으로 Credentials는 방금 생성한 토큰정보로 입력해준다.

 

모든 입력이 끝났다면 Test connection 버튼을 눌러보자!

 

 

여기까지 완료되었다면 Jenkins&Github 연동이 완료되었다. 조금 긴 글이었기 때문에 여기서 한번 끊고 다음 포스팅에서 이어서 Jenkins와 Github을 이용한 CI 실습을 이어나가겠다.

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 2. 24. 00:25

오늘 다루어볼 내용은 쿠버네티스 환경에서의 로깅운영 방법이다. 지금까지는 쿠버네티스에 어떻게 팟을 띄우는지에 대해 집중했다면 오늘 포스팅 내용은 운영단계의 내용이 될 것 같다. 사실 어떻게 보면 가장 중요한 내용중에 하나라고 볼 수 있는 것이 로깅이다. 물리머신에 웹을 띄울 때는 파일로 로그를 날짜별로 남기고, 누적 일수이상된 파일은 제거 혹은 다른 곳으로 파일을 옮기는 등의 작업을 했을 것이다. 하지만 쿠버네티스에서는 파일로 로그를 남기지 않으며 조금 다른 방법으로 로깅운영을 진행한다. 

 

컨테이너 환경에서 로그를 운영하는 구체적인 방법을 설명하기 전에 컨테이너 환경에서 로그가 어떻게 생성되는지 알아본다. 비컨테이너 환경의 애플리케이션에서는 보통 로그를 파일로 많이 남기고 한다. 이에 비해 도커에서는 로그를 파일이 아닌 표준 출력으로 출력하고 이를 다시 Fluentd 같은 로그 컬렉터로 수집하는 경우가 많다. 이런 방법은 애플리케이션 쪽에서 로그 로테이션이 필요없으며 로그 전송을 돕는 로깅 드라이버 기능도 갖추고 있으므로 로그 수집이 편리하다.

 

간단하게 필자가 만든 이미지로 실습을 진행한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> docker pull 1223yys/springboot-web:0.2.5
 
> docker container run -it --rm -p 8080:8080 1223yys/springboot-web:0.2.5
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.1.RELEASE)
 
2020-02-23 11:29:15.368  INFO 1 --- [           main] com.kebe.sample.SampleApplication        : Starting SampleApplication on d1e5a6d24e34 with PID 1 (/app/app.jar started by root in /)
2020-02-23 11:29:15.375  INFO 1 --- [           main] com.kebe.sample.SampleApplication        : No active profile set, falling back to default profiles: default
2020-02-23 11:29:17.099  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-02-23 11:29:17.122  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-23 11:29:17.124  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.27]
2020-02-23 11:29:17.244  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-23 11:29:17.245  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1784 ms
2020-02-23 11:29:17.619  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-23 11:29:17.885  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-23 11:29:17.890  INFO 1 --- [           main] com.kebe.sample.SampleApplication        : Started SampleApplication in 3.221 seconds (JVM running for 3.936)
cs

 

 

yoonyeoseong/kubernetes-sample

Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

우선 이미지를 내려받고 애플리케이션을 포어그라운드로 실행시킨다. 그 다음 로그가 호스트에서 어떻게 출력되는지 확인해보자.

 

그전에 아래의 명령으로 실행 중인 컨테이너들의 로그가 어떻게 찍히는 지 확인할 수 있다.

 

1
> docker run -it --rm -v /var/lib/docker/containers:/json-log alpine ash
cs

 

명령 실행 후에 /json-log 디렉토리에 위에서 실행한 컨테이너의 아이디 디렉토리로 들어가면 현재 우리가 표준출력으로 찍고 있는 로그가 json 타입으로 찍히고 있으며 이 표준출력이 파일로 남고 있는 것을 볼 수 있다.

 

 

다시 말해, 애플리케이션에서 로그를 파일로 출력하지 않았더라도 도커에서 컨테이너의 표준 출력을 로그로 출력해주는 것이다. 따라서 로그 출력 자체를 완전히 도커에 맡길 수 있다.

 

도커 로깅 드라이버

도커 컨테이너의 로그가 JSON 포맷으로 출력되는 이유는 도커에 json-file이라는 기본 로깅 드라이버가 있기 때문이다. 로깅 드라이버는 도커 컨테이너가 출력하는 로그를 어떻게 다룰지를 제어하는 역할을 한다. json-file 외에도 다음과 같은 로깅 드라이버가 존재한다.

 

logging driver description
Syslog syslog로 로그를 관리
Journald systemd로 로그를 관리
Awslogs AWS CloudWatch Logs로 로그를 전송
Gcplogs Google Cloud Logging으로 로그를 전송
Fluentd fluentd로 로그를 관리

 

도커 로그는 fluentd를 사용해 수집하는 것이 정석이다. 

 

컨테이너 로그의 로테이션

애플리케이션에서 표준 출력으로 출력하기만 해도 로그를 파일에 출력할 수 있지만, 웹 애플리케이션처럼 컨테이너 업타임이 길거나 액세스 수에 비례해 로그 출력량이 늘어나는 경우에는 JSON 로그 파일 크기가 점점 커진다. 컨테이너를 오랜 시간 운영하려면 이 로그를 적절히 로테이션할 필요가 있다.

 

도커 컨테이너에는 로깅 동작을 제어하는 옵션인 --log-opt가 있어서 이 옵션으로 도커 컨테이너의 로그 로테이션을 설정할 수 있다. max-size는 로테이션이 발생하는 로그 파일 최대 크기이며 l/m/g 단위로 파일 크기 지정이 가능하다. max-file은 최대 파일 개수를 의미하며 파일 개수가 이 값을 초과할 경우 오래된 파일부터 삭제된다.

 

1
docker container run -it --rm -p 8080:8080 --log-opt max-size=1m --log-opt max-file=5 1223yys/springboot-web:0.2.5
cs

 

이 설정을 매번 컨테이너 실행마다 할 필요는 없고, 도커 데몬에서 log-opt를 기본값으로 설정할 수 있다. Preference 화면에서 Deamon > Advanced 항목에서 다음과 같이 JSON 포맷으로 설정할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
{
  "experimental" : false,
  "debug" : true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "1m",
    "max-file": "5"
  }
}
 
cs

 

쿠버네티스에서 로그 관리하기

다른 예제는 뛰어넘고, 바로 쿠버네티스 환경에서 로그관리하는 방법을 알아본다. 우선 가장 대중적인 쿠버네티스 로그 운영은 아래와 같은 플로우로 많이 진행하는 것 같다.

 

app ---> fluentd ---> elasticsearch ---> kibana

 

app에서는 표준 출력으로 로그를 출력하고 fluentd는 로그를 긁어서 엘라스틱서치에 색인한다. 그리고 키바나를 통해 색인된 로그들을 모니터링한다. 쿠버네티스의 로그 관리에서도 역시 컨테이너는 표준 출력으로만 로그를 내보내면 되며, 그에 대한 처리는 컨테이너 외부에서 이루어진다.

 

쿠버네티스는 다수의 도커 호스트를 노드로 운영하는데, 어떤 노드에 어떤 파드가 배치될지는 쿠버네티스 스케줄러가 결정한다. 그러므로 각 컨테이너가 독자적으로 로그를 관리하면 비용이 많이 든다. 먼저 로컬 쿠버네티스 환경에 Elasticsearch와 Kibana를 구축한 다음, 로그를 전송할 fluentd와 DaemonSet을 구축한다.

 

쿠버네티스에 Elasticsearch와 Kibana 구축하기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: elasticsearch-pvc
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2G
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: kube-system
spec:
  selector:
    app: elasticsearch
  ports:
  - protocol: TCP
    port: 9200
    targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: kube-system
  labels:
    app: elasticsearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:5.6-alpine
        ports:
        - containerPort: 9200
          name: http
        volumeMounts:
        - mountPath: /data
          name: elasticsearch-pvc
        - mountPath: /usr/share/elasticsearch/config
          name: elasticsearch-config
      volumes:
      - name: elasticsearch-pvc
        persistentVolumeClaim:
          claimName: elasticsearch-pvc
      - name: elasticsearch-config
        configMap:
          name: elasticsearch-config
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: elasticsearch-config
  namespace: kube-system
data:
  elasticsearch.yml: |-
    http.host: 0.0.0.0
    path.scripts: /tmp/scripts
  log4j2.properties: |-
    status = error
    appender.console.type = Console
    appender.console.name = console
    appender.console.layout.type = PatternLayout
    appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
    rootLogger.level = info
    rootLogger.appenderRef.console.ref = console
  jvm.options: |-
    -Xms128m
    -Xmx256m
    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=75
    -XX:+UseCMSInitiatingOccupancyOnly
    -XX:+AlwaysPreTouch
    -server
    -Xss1m
    -Djava.awt.headless=true
    -Dfile.encoding=UTF-8
    -Djna.nosys=true
    -Djdk.io.permissionsUseCanonicalPath=true
    -Dio.netty.noUnsafe=true
    -Dio.netty.noKeySetOptimization=true
    -Dio.netty.recycler.maxCapacityPerThread=0
    -Dlog4j.shutdownHookEnabled=false
    -Dlog4j2.disable.jmx=true
    -Dlog4j.skipJansi=true
    -XX:+HeapDumpOnOutOfMemoryError
cs

 

엘라스틱서치 manifast 파일이다. 볼륨마운트, 컨피그 맵 등의 설정이 추가적으로 들어갔다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: kube-system
spec:
  selector:
    app: kibana
  ports:
  - protocol: TCP
    port: 5601
    targetPort: http
    nodePort: 30050
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: kube-system
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: kibana:5.6
        ports:
        - containerPort: 5601
          name: http
        env:
        - name: ELASTICSEARCH_URL
          value: "http://elasticsearch:9200"
cs

 

키바나 manifast 파일이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      app: fluentd-logging
  template:
    metadata:
      labels:
        app: fluentd-logging
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:elasticsearch
        env:
          - name: FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch"
          - name: FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          - name: FLUENT_UID
            value: "0"
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
cs

 

마지막으로 fluentd manifast이다. DaemonSet 으로 구성되며 클러스터 노드마다 할당되는 리소스이다. DaemonSet은 클러스터 전반적인 로깅등의 작업을 수행하는 리소스에 할당하는 타입이다. 어떠한 팟마다 같이 뜨는 리소스가 아니고 클러스터 노드마다 뜨는 리소스라고 보면 된다. 로그 컬렉터같이 호스트마다 특정할 역할을 하는 에이전트를 두고자 할 때 적합하다.

 

여기까지 간단하게 쿠버네티스 환경에서 로깅 운영하는 방법을 간단하게 다루어봤다. 다음 예제에서는 기본적인 플로우 이외로 output을 kafka로 보내는 등의 다른 플러그인을 사용하여 로그를 수집하는 예제를 살펴볼 것이다.

posted by 여성게
:
인프라/Docker&Kubernetes 2019. 11. 26. 23:39

 

이전 포스팅에서 쿠버네티스에 대한 용어와 개념을 다루어봤는데, 이번 포스팅에서는 실제 쿠버네티스가 어떤 구조로 되어 있는지 아키텍쳐에 대해 다루어본다.

2019/11/26 - [인프라/Docker&Kubernetes] - Kubernetes - Kubernetes 용어설명

 

Kubernetes - Kubernetes 용어설명

지금까지 쿠버네티스에 대한 포스팅을 여러개 했는데, 더 진행하기 앞서 쿠버네티스에서 사용하는 용어 및 개념들을 정리하면 좋을듯 해서 포스팅한다. 마스터&노드 쿠버네티스의 클러스터의 구조에서 전체 클러..

coding-start.tistory.com

 

 

마스터&노드

쿠버네티스 클러스터는 크게 마스터와 노드로 구성된다. 마스터는 쿠버네티스 클러스터의 전반적인 것을 관리하고 노드는 Pod이나 Service등처럼 쿠버네티스 위에서 동작하는 워크로드를 호스팅하는 역할을 한다.

 

1) 마스터

클러스터 전체를 관장하는 시스템이며 크게 API 서버, 스케줄러, 컨트롤러 매니저, etcd로 구성되어 있다.

 

-API서버 : 쿠버네티스는 모든 명령과 통신을 API를 통해서 하는데, 그 중심이 되는 서버가 API 서버이다.

-Etcd : 쿠버네티스 클러스터의 데이터 베이스 역할을 하는 Key/Value Store이다. 쿠버네티스 클러스터의 상태나 설정 정보를 저장한다.

-스케줄러 : 스케줄러는 Pod, 서비스 등 각 리소스들을 적절한 노드에 할당하는 역할을 한다.

-컨트롤러 매니저 : 컨트롤러 매니저는 컨트롤러(RC,SC,VC,NC)를 생성하고 이를 각 노드에 배포하며 관리하는 역할을 한다.

 

-DNS : 쿠버네티스는 리소스의 엔드포인트를 DNS로 맵핑하고 관리한다. Pod이나 서비스등은 IP를 배정받는데, 동적으로 생성되는 값이기 때문에 그 리소스에 대한 정보를 DNS로 해결한다. 이러한 패턴을 Service Discovery 패턴이라 한다. 새로운 리소스가 생성되면, 그 리소스의 IP와 DNS를 매핑하여 등록하고 DNS 이름을 기반으로 리소스에 접근할 수 있도록 한다.

 

 

2) 노드

노드는 마스터에 의해 명령을 받고 실제 워크로드를 생성하여 서비스 하는 시스템이다. 노드에는 Kubelet, Kube-Proxy, cAdvisor 그리고 컨테이너 런타임이 배포된다.

 

-Kubelet : 노드에 배포되는 에이전트로, 마스터의 API서버와 통신을 하면서, 노드가 수행해야 할 명령을 받아서 수행하고, 반대로 노드의 상태등을 마스터로 전달하는 역할을 한다.

-Kube-Proxy : 노드로 들어오는 네트워크 트래픽을 적절한 컨테이너로 라우팅하고, 로드밸런싱등 노드로 들어오고 나가는 네트워크 트래픽을 프록시하며 노드와 마스터간의 네트워크 통신을 관리한다.

-Container Runtime : Pod을 통해 배포된 컨테이너를 실행하는 컨테이너 런타임이다.

-cAdvisor : 각 노드에서 기동되는 모니터링 에이전트로, 노드내에서 기동되는 컨테이너들의 상태와 성능등의 정보를 수집하여, 마스터 서버의 API 서버로 전달한다.

 

여기까지 쿠버네티스 클러스터의 구조에 대해 간단히 살펴봤다. 사실 쿠버네티스를 사용할 때, 이 정보들을 몰라도 사용이 가능할지는 모르겠지만, 원리 혹은 구동방식, 아키텍쳐를 알고 모르고는 이슈 트랙킹하는 데 큰 차이가 있다고 생각이 들기 때문에 꼭 알고 넘어가야하는 개념들인 것 같다.

posted by 여성게
:
인프라/Docker&Kubernetes 2019. 11. 26. 23:24

 

지금까지 쿠버네티스에 대한 포스팅을 여러개 했는데, 더 진행하기 앞서 쿠버네티스에서 사용하는 용어 및 개념들을 정리하면 좋을듯 해서 포스팅한다.

 

마스터&노드

쿠버네티스의 클러스터의 구조에서 전체 클러스터를 관리하는 마스터 노드가 있고, 도커 컨테이너가 배포되는 노드가 존재한다.

 

 

Pod

Pod는 쿠버네티스에서 가장 기본적인 배포 단위로, 하나 이상의 도커 컨테이너의 묶음이다. 쿠버네티스는 컨테이너 단위로 배포하는 것이 아니라, Pod이라는 하나 이상의 도커 컨테이너로 이루어진 단위로 배포가 된다. 

 

그렇다면 개별적인 컨테이너로 배포하지 않고, Pod이라는 단위로 배포하는 이유는 무엇일까?

 

  • Pod 내의 컨테이너는 IP와 Port를 공유한다. 즉, 서로 localhost로 통신이 가능한 것이다.
  • Pod 내에 배포된 컨테이너 간에는 디스크 볼륨 공유가 가능하다. 

위와 같은 특징을 가지고 있기 때문에 보통 하나 이상의 컨테이너를 포함한 Pod 단위로 배포한다. 그렇다면 이러한 예제로는 어떤 것이 있을 까? 요즘 애플리케이션을 배포할 때는 애플리케이션 하나만 배포하는 것이 아니라, Nginx와 로그 수집기, 예거 등을 같이 배포하는 경우가 많은 데, 보통 이런 조합은 디스크를 공유해야 하거나 서로 HTTP 통신을 해야한다. 하지만 이럴때 서로 다른 컨테이너로 배포할 경우 굉장히 구성이 까다로워질 것이다. 즉, 하나의 Pod 내에 여러 컨테이너로 띄울 경우 위 문제가 어느정도 해결된다.

 

이렇게 애플리케이션과 애플리케이션이 사용하는 주변 프로그램을 같이 배포하는 패턴을 MSA(Micro Service Architecture)에서 사이드카패턴이라고 한다.

 

Volume

Pod가 뜰때, 컨테이너마다 로컬 디스크를 생성해서 기동되는데, 이 로컬 디스크의 경우 영구적이지 못하다. 즉, 컨테이너가 리스타트 되거나 새로 배포될때 로컬 디스크의 내용이 유실된다. 그런데 보통 데이터베이스와 같이 영구적으로 파일을 저장해야 하는 경우에는 컨테이너의 리스타트와 상관없이 파일을 영구적으로 저장해야 하는데, 이럴때 볼륨을 마운트해서 사용한다. 즉, Pod에 볼륨을 할당한다는 뜻이다. 이러한 볼륨은 하나의 Pod 내에서 여러 컨테이너가 공유가능하다는 특징을 가지고 있다.

 

Service

보통의 경우 하나의 Pod으로만 서비스를 제공하지 않는다. 보통은 여러개의 동일한 Pod을 띄워 로드밸런싱하여 서비스를 제공한다. 이럴 경우에 Pod은 동적으로 생성되고 삭제되고, 생성되는 IP는 그때그때 다르기 때문에 이러한 Pod의 디스커버리를 IP로 묶는 것을 쉽지 않다. 그래서 사용되는 것이 Service이고 이는 내부적으로 라벨과 라벨 셀렉터를 이용하여 가용 가능한 Pod을 디스커버리한다.

 

서비스를 정의할때, 어떤 Pod을 하나의 서비스라는 개념으로 묶을 것인지 정의하는데, 이를 라벨 셀렉터라고 한다. 각 Pod의 Object를 정의할 때, 메타정보에 라벨정보를 넣을 수 있는데, 이 라벨을 Service Object를 정의할때 라벨셀렉터에 넣어준다. 이러면 동일한 라벨을 같은 여러개의 같은 Pod이 동일한 Service 단위로 묶이게 되는 것이다.

 

 

Namespace

네임스페이스는 한 쿠버네티스 클러스터 내의 논리적인 분리단위이다. Object들을 네임스페이스 단위로 별도 생성 및 관리가 가능하고, 사용자의 권한 역시 이 네임스페이스 단위로 부여할 수 있다. 보통 개발/운영/테스트 환경을 간혹 네임스페이스 단위로 나누기도 하는데, 왠만하면 클러스터 자체를 분리하는 것이 좋다. 네임스페이스로 나눈다고 해서 물리적인 환경을 분리하는 것이 아니고 단순 논리적인 분리밖에 되지 않아 머신의 리소스를 같이 사용하기 때문이다.

 

Label

라벨은 쿠버네티스의 리소스를 선택하는데 사용된다. 각 리소스는 라벨을 가질 수 있고, 라벨 검색 조건에 따라 특정 라벨을 가진 리소스만 선택이 가능하다. 라벨은 metadata 부분에 키/값으로 정의 가능하며 하나의 리소스에는 하나 이상의 라벨을 가질 수 있다.

 

"metadata": {
	"labels": {
    	"key1": "value1",
        "key2": "value2"
    }
}

 

라벨 셀렉터를 사용하는 방법으로 2가지 방법을 제공한다. 하나는 Equality based selector와, Set based selector가 있다.

 

Equality based selector는 단순히 같고,다르다는 조건을 이용하여 라벨을 셀렉하는 방법이다.

  • env = dev
  • env != product

Set based selector는 집합 개념을 이용한다.

  • env in (dev,qa)
  • env notin (product,stage)

 

Controller

컨트롤러는 기본 오브젝트(Pod,Service,Volume,Namespace)들을 생성하고 관리하는 역할을 한다. 컨트롤러는 Replicaion Controller, Replication Set, DaemonSet, Job, StatefulSet, Deployment 들이 있다.

 

1) Replication Controller

Replication Controller는 Pod을 관리해주는 역할을 하는데, 지정된 숫자로 Pod을 기동시키고, 관리하는 역할을 한다. Replication Controller는 크게 3가지 파트로 구성되는데, Replica의 수, Pod Selector, Pod Template(Spec)으로 구성된다.

 

 

이미 기동된 Pod이 있다면 주의해야할 점이 있다. Pod이 이미 떠있는 상태에서 RC 리소스를 생성하고 그 Pod의 라벨이 RC와 라벨 셀렉터와 일치하면 Pod들은 새롭게 생성된 RC의 컨트롤을 받는다. 만약 해당 Pod들이 RC에서 정의한 replica 수보다 많으면, replica 수에 맞게 Pod들을 삭제하고, 모자르면 template(Spec)에 정의된 되로 Pod을 띄우게 된다. 하지만 여기서 주의할 점이 template(Spec)에 정의된 Pod 리소스 설정과 라벨셀렉터에 의해 픽된 Pod의 리소스 설정이 다르더라도 기존 Pod을 내렸다가 올리지 않는 다는 점이다. 이말은 즉, 싱크가 맞지 않는 형상이 다른 Pod이 뜰 가능성이 있다.

 

2) ReplicaSet

ReplicaSet은 RC의 새버전이라 생각하면 된다. RC는 Equality 기반 셀럭터를 사용하는데, ReplicaSet은 Set 기반 셀렉터를 이용한다.

 

3) Deployment

Deployment는 RC와 RS의 좀더 상위 개념이다. 셀제 운영에서는 RC,RS 보다 Deployment를 이용한다. Deployment를 설명하기 전에 배포 방식에 대한 이해가 필요하므로 설명한다.

 

 

위와 같은 RC와 Pod들이 있다고 생각하고 새로운 버전의 Pod을 띄우는 상황이라 가정하자. 보통이런 상황에서 Blue/Green 배포와 Rolling Update 배포 방식을 이용한다.

 

Blue/Green deployment

블루그린 배포 방식은 블루(예전) 버전으로 서비스 하고 있던 시스템은 그린(새로운)버전을 배포한 후, 트래픽을 블루에서 그린으로 한번에 돌리는 방식이다. 여러가지 방법이 있지만 가장 쉬운 방법은 새로운 RC와 Pod들을 모두 띄운 후에 Service의 트래픽을 한번에 새로운 Pod으로 돌리는 방법이다.

 

배포후 문제가 없다면 이전 RC와 Pod들을 내려준다.

 

Rolling update deployment

롤링 업데이트 배포 방식은 Pod을 하나씩 업그레이드 하는 방식이다. 이렇게 배포를 하려면 먼저 새로운 RC를 만든 후에, 기존 RC에서 replica 수를 하나 줄이고, 새로운 RC에는 replica 수를 하나 늘려준다. 이런식으로 하나 줄이고 하나늘리고 하는 방식으로 배포를 하게된다.

 

 

만약 배포가 잘못되었다면 기존 RC의 replica를 원래대로 올리고, 새버전의 replica 수를 0으로 만들어 롤백한다. 이런 롤링 업데이트 배포 방식을 RC 단위로도 가능하지만 여러가지 작업이 필요하다. 그래서 이러한 여러 과정을 추상화한 개념을 Deployment라고 보면 된다.

 

4) DaemonSet

DaemonSet은 Pod가 각각의 노드에서 하나씩만 돌게하는 형태로 Pod을 관리하는 컨트롤러이다. RC, RS 혹은 Deployment로 관리되는 Pod들은 클러스터 노드의 리소스 상황에 따라 비균등적으로 배포되는데, DS에 의해 관리되는 Pod는 모든 노드에 균등하게 하나씩만 배포된다. 보통 서버 모니터링 혹은 로그 수집용도로 많이 사용된다.

 

DS의 다른 특징중 하나는 특정 Node들에만 Pod들을 하나씩 배포되도록 설정이 가능 하다. 이렇게 특정 노드를 셀렉하기 위해 node selector를 제공하여 특정 노드를 선택가능하도록 지원한다.

 

 

 

여기까지 간단히 쿠버네티스에서 사용되는 용어들을 다루어봤다. 사실 다루지 못한 용어들이 많이 있지만 추후에 더 다루어볼 예정이다. 다음 포스팅에서는 쿠버네티스 아키텍쳐에 대해 다루어볼 것이다.

posted by 여성게
: