docker image 빌드환경 구현실습

2019-12-03

.

Data_Engineering_TIL_(20191202)

[학습한 Contents]

1) 주제 : 컨테이너 기반 가상화 플랫폼 ‘도커(Doker)’의 이해 - 도커 이미지 빌드 환경 만들기 / SK T아카데미 온라인강의

2) URL : https://tacademy.skplanet.com/live/player/onlineLectureDetail.action?seq=125

[필기노트]

  • 젠킨스를 이용해서 도커이미지를 자동으로 빌드하는 실습을 해보자

  • CI란 제품을 빨리빨리 출시를 해야하는데 그거를 좀더 효과적으로 소스를 통합하고, 빌드하고, 테스트하고 배포하는 과정을 말하고, CD는 빌드후에 배포까지를 얘기할때 보통 쓴다. 그래서 통상적으로 이런 일련의 과정을 CI/CD라고 많이 부르게 된다.

1

  • 일반적인 패턴으로는 개발자가 코드를 수정하고, 깃허브에다가 올리게 되면 깃허브가 빌브하는 CI를 호출하게 되면 호출된 CI가 이미지를 만든다던가 테스트를 하는 과정을 거쳐 최종적으로는 서버에 배포하는 과정을 CI가 해준다. 지난시간까지 도커 이미지를 빌드하고 배포하는 것을 배웠는데 이 과정을 CI가 대신해준다고 생각하면 된다.

2

  • 실제로 도커를 사용하는 팀에서 제품을 배포하는 과정을 보면 아래 그림과 같다.

개발자가 먼저 소스저장소에 최신소스를 푸쉬하면, 이거를 누군가가 다운받아서 테스트 코드를 돌려서 정상적으로 돌아가는지 확인하고, 그 소스가 정상적으로 돌아가면 그거를 가지고 도커이미지를 생성하고 도커이미지를 도커 허브에 푸쉬한 다음에 각 서버에 떠있는 컨테이너를 새 이미지로 업데이트 하는 과정을 거치게 된다.

그러나 이런과정들을 자동화한다고 하면 소스저장소에 최신소스를 저장하는 것까지만 개발자가 신경을 쓰고, 그 이후는 젠킨스라는 CI한테 맡겨서 자동으로 애플리케이션 업데이트까지 수행을 해줄 수 있다.

3

  • 가장많이 쓰는 CI도구는 젠킨스, TravisCI, CircleCI 등이 있다.

  • 가장 많이쓰는 대표주자로 젠킨스가 있는데 다음과 같다.

젠킨스의 기본적인 아키텍처는 마스터가 하나가 있고 여러개의 에이전트를 구성할 수 있어서 빌드가 빈번하거나 많이 이루어지는 경우에는 서버를 여러대로 구성을 해서 각 서버마다 에이전트를 심어놓고, 마스터에서 빌드를 하면 여러대의 에이전트에 분산해서 빌드나 테스트하는 작업을 처리하게 된다.

4

  • 그래서 우리는 지금부터 루비로 만든 어플리케이션을 가지고, 어떻게 CI가 자동으로 배포까지 해주는지 실습해볼 것이다.

먼저 EC2 서버를 띄우고, 도커를 설치한 다음에 아래와 같이 젠킨스를 설치해준다.

아래 그림에서 참고로 docker run -u root --rm -p 8080:8080 --name jenkins -v <임의의리눅스디렉토리>:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock subicura/jenkins:2 에서 -v 옵션으로 리눅스의 /var/run/docker.sock를 젠킨스의 /var/run/docker.sock로 맵핑하겠다는 것이다. 이 docker.sock 파일이 도커와 통신할때 사용하는 파일인데 이거를 젠킨스에도 맵핑을 함으로써 젠킨스에는 실제로 도커가 설치되어 있지는 않지만 호스트에 있는 도커를 젠킨스가 사용할 수 있도록 해주는 것이다. 마찬가지로 <임의의리눅스디렉토리>를 /var/jenkins_home에 맵핑하겠다는 것이다.

5

  • 위와 같이 젠킨스를 설치했으면 젠킨스가 일을 할 수 있도록 작업을 만들것이다. 우리는 파이프라인이라는 작업을 만들것이다 아래 그림을 보면 마치 어떤 작업들이 파이프라인처럼 연결이 되어서 일련의 과정을 통해 처리가 되는것을 볼 수 있다. 아래 그림의 과정을 참고해서 빌드하고 배포하는 파이프라인을 만들어볼 것이다.

7

참고로 파이프라인을 스크립트로 작성하게 되는데 스크립트는 그루비라는 언어로 되어 있다.

그래서 아래 그림과 같이 진행해준다.

마지막 단계를 보면 빌드는 성공했으나 아무것도 일어나지 않았다는 메세지를 볼 수 있는데 이는 우리가 젠킨스한테 무슨일을 하라고 구체적인 지시를 따로 주지 않았기 때문이다.

6

그래서 스테이지를 구성해서 일을 어떤거를 할지 부여해보자.

stage 1 : 깃으로부터 소스코드를 다운받는다.

stage 2 : 다운받은 코드를 갖고 유닛테스트를 실시한다.

stage 3 : 도커빌드를 실시한다.(도커 이미지를 빌드(생성)한다)

stage 4 : 도커테그를 현재 빌드숫자로 표시할 것이다.

stage 5 : 도커 푸쉬로 도커허브에 이미지를 올린다.

stage 6 : 푸쉬한 도커이미지를 배포하여 컨테이너를 구동한다.

먼저 아래 그림과 같이 빈 스테이지를 구성을 해보겠다. 아래 그림과 같이 진행을 해주면 아까와는 다르게 정의한 스테이지별로 작업되는 과정에 대한 화면이 살짝 바뀐것을 볼 수 있다.

8

그러면 아래 그림과 같이 각 단계별로 코드를 작성해서 구체적으로 스테이지별로 어떤작업을 할건지 명시해보자.

먼저 pull stage에서는 아래와 같이 깃허브 주소를 입력해줘서 깃허브에서 소스코드를 가져와 젠킨스 워크스페이스에 업로드를 시킨다.

9

아래 그림과 같이 build stage까지 작성하고 테스트를 해본 결과이다. 이렇게 각 단계별로 하나씩 스테이지를 작성하고 테스트를 해볼 수 있다.

그리고 각 스테이지 별로 어떤 작업이 구체적으로 이루어졌는지 로그도 볼 수 있다.

10

파이프라인에서 사용할 도커허브 계정을 아래 그림과 같이 등록해준다.

11

12

아래 그림과 같이 전체적으로 성공했으나 빌드했을때 에러가 나는 이유는 도커스탑 명령을 날렸더니 해당 컨테이너가 없다그래서 try catch에 걸린것이고, 그 밑에 로그를 보면 컨테이너가 존재하지 않다고 나오는 것이다. 그리고 docker run을 실행하게 된다.

그래서 아래 그림과 같이 실제 10000번 포트에 접속해서 루비서버가 잘 뜨는지 확인해본다. 루비서버에 나온 문자열은 컨테이너의 아이디를 말하는 것이다.

13

중간에 워크스페이스 경로설정을 안해줬는데 반드시해준다. 위에 PDF 슬라이드 참고

13-2

실제 업무에서는 위와 같이 하지않고 docker-compose.yml을 이용하는 편이다.

14

설정파일부분은 아래와 같이 해주면 된다.

node {
    withCredentials([[$class: 'UsernamePasswordMultiBinding', 
    credentialsId: 'dockerhub', 
    usernameVariable: 'DOCKER_USER_ID', 
    passwordVariable: 'DOCKER_USER_PASSWORD']]) {
        stage('Pull') {
            git 'https://github.com/subicura/docker-jenkins-workshop.git'
        }
        stage('Unit Test') {
            sh(script: 'docker-compose run --rm unit')
        }
        stage('Build') {
            sh(script: 'docker-compose build app')
        }
        stage('Tag') {
            sh(script: 'docker tag ${DOCKER_USER_ID}/ruby-app ${DOCKER_USER_ID}/ruby-app:${BUILD_NUMBER}')
        }
        stage('Push') {
            sh(script: 'docker login -u ${DOCKER_USER_ID} -p ${DOCKER_USER_PASSWORD}')
            sh(script: 'docker push ${DOCKER_USER_ID}/ruby-app:${BUILD_NUMBER}')
            sh(script: 'docker push ${DOCKER_USER_ID}/ruby-app:latest')
        }
        stage('Deploy') {
            sh(script: 'docker-compose up -d production')
        }
    }    
}

15

또하나 개선사항으로는 지금까지는 계속 젠킨스 콘솔에서 빌드나우를 눌러줘서 빌드를 시켜줬는데 이거를 이제 소스가 수정될때마다 자동으로 일을하게 할 수 있다.

위의 예시와 같이 주기적으로 깃헙에서 변경사항이 있는지 체크해서 바뀐게 있으면 빌드가 실행하도록 트리거를 지정할 수 있다.

node {
    git poll: true, url: 'https://github.com/subicura/docker-jenkins-workshop.git'
    
    withCredentials([[$class: 'UsernamePasswordMultiBinding', 
    credentialsId: 'dockerhub', 
    usernameVariable: 'DOCKER_USER_ID', 
    passwordVariable: 'DOCKER_USER_PASSWORD']]) {
        stage('Pull') {
            git 'https://github.com/subicura/docker-jenkins-workshop.git'
        }
        stage('Unit Test') {
            sh(script: 'docker-compose run --rm unit')
        }
        stage('Build') {
            sh(script: 'docker-compose build app')
        }
        stage('Tag') {
            sh(script: 'docker tag ${DOCKER_USER_ID}/ruby-app ${DOCKER_USER_ID}/ruby-app:${BUILD_NUMBER}')
        }
        stage('Push') {
            sh(script: 'docker login -u ${DOCKER_USER_ID} -p ${DOCKER_USER_PASSWORD}')
            sh(script: 'docker push ${DOCKER_USER_ID}/ruby-app:${BUILD_NUMBER}')
            sh(script: 'docker push ${DOCKER_USER_ID}/ruby-app:latest')
        }
        stage('Deploy') {
            sh(script: 'docker-compose up -d production')
        }
    }    
}

만약에 내가 작성한 설정파일 스크립트를 누군가가 실수로 삭제할 수도 있다. 이 젠킨스 기능중에 소스폴더에 있는 파일을 그냥 사용하는 기능이 있다. 그래서 특정 깃허브를 지정해서 파일은 어떤거를 쓸건지 아래 그림과 같이 지정하면 젠킨스에서 소스코드를 관리하는것이 아니라 깃허브에서 관리하게 된다.

16

실제사례는 어떻게 하느냐.

실제 프로덕션 서버에 배포하는 것은 불안한 부분이 있기 때문에 chatops라는 것을 이용해서 빌드가 완료되면 메세지를 줄 수 있다. 그러면 /deploy-production 명령을 아래 그림과 같이 날리게 되면 디플로이 작업을 할 수 있다.

또한 슬랙도 이용할 수 있다.

17

아래와 같은 전략은 큰회사에서 많이 하는 전략이다.

예를들어서 깃의 어떤 하나의 브랜치를 푸쉬하면 해당 브랜치별로 이미지를 만들어서 배포하고 엔진엑스에다가 설정파일을 바꿔서 http://브랜치이름.test.com 으로 들어왔을때 해당이미지를 볼수있게한다.

18

우리가 지금까지 했던 실습들은 중단배포라고 할 수 있다.

도커 컨테이너를 여러개 동시에 띄울 수 있기 때문에 무중단 배포도 가능하다.

19

대규모의 서버에 대규모의 도커를 배포할때는 도커스웜이나 쿠버네티스를 사용하게 된다.