인프라/Docker&Kubernetes 2020. 4. 21. 11:43

이번 포스팅에서 다루어볼 내용은 DOOD로 도커를 띄웠을 때, proxy 설정하는 방법이다. 그전에 간단하게 Docker in Docker(dind)와 Docker Out Of Dcoker(DooD)에 대해 알아보자.

 

Docker in Docker(dind)

도커 내부에 격리된 Docker 데몬을 실행하는 방법이다. CI(Jenkins docker agent) 측면에서 접근하면 Agent가 Docker client와 Docker Daemon 역할 두가지를 동시에 하게 된다. 하지만 이 방법은 단점이 존재한다. 내부의 도커 컨테이너가 privileged mode로 실행되어야 한다.

 

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

 

privileged 옵션을 사용하면 모든 장치에 접근할 수 있을뿐만 아니라 호스트 컴퓨터 커널의 대부분의 기능을 사용할 수 있기 때문에 보안에 좋지않은 방법이다. 하지만 실제로 실무에서 많이 쓰는 방법이긴하다.

 

Docker out of Docker(dood)

Docker out of Docker는 호스트 머신에서 동작하고 있는 Docker의 Docker socket과 내부에서 실행되는 Docker socket을 공유하는 방법이다. 간단하게 볼륨을 마운트하여 두 Docker socket을 공유한다.

 

> docker run -v /var/run/docker.sock:/var/run/docker.sock ...

 

이 방식을 그나마 Dind보다는 권장하고 있는 방법이긴하다. 하지만 이 방식도 단점은 존재한다. 내부 도커에서 외부 호스트 도커에서 실행되고 있는 도커 컨테이너를 조회할 수 있고 조작할 수 있기 때문에 보안상 아주 좋다고 이야기할 수는 없다.

 

DooD proxy 설정

dood로 도커를 띄운 경우, 호스트 머신에 동작하고 있는 도커에 proxy 설정이 되어있어야 내부 도커에도 동일한 proxy 설정을 가져간다.

 

방법1. /etc/sysconfig/docker에 프록시 설정
> sudo vi /etc/sysconfig/docker
HTTP_PROXY="http://proxy-domain:port"
HTTPS_PROXY="http://proxy-domain:port"

#docker restart
> service docker restart

 

방법2. 환경변수 설정
> mkdir /etc/systemd/system/docker.service.d
> cd /etc/systemd/system/docker.service.d
> vi http-proxy.conf

[Service]
Environment="HTTP_PROXY=http://proxy-domain:port"
Environment="HTTPS_PROXY=http://proxy-domain:port"
Environment="NO_PROXY=hostname.example.com, 172.10.10.10"

> systemctl daemon-reload
> systemctl restart docker

> systemctl show docker --property Environment
Environment=GOTRACEBACK=crash HTTP_PROXY=http://proxy-domain:port HTTPS_PROXY=http://proxy-domain:port NO_PROXY= hostname.example.com,172.10.10.10

 

여기까지 dood로 도커 엔진을 띄웠을 때, proxy 설정하는 방법이다.

 

 

참고

 

How to configure docker to use proxy – The Geek Diary

 

www.thegeekdiary.com

 

posted by 여성게
:
인프라/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 여성게
: