Kubeflow에는 kfp라는 Python SDK가 존재하며 이를 통해 도커 이미지 없이 ML pipeline을 구축할 수 있다. 하지만 딥러닝을 수행할 경우 코드 길이가 길어져 kfp로 코드 작성 및 수정이 어려워지고 큰 데이터셋에 대한 component간의 데이터 전달이 제한적이다.

이러한 경우 전처리, 하이퍼파라미터 튜닝, 학습, 검증을 각각 수행하는 코드를 작성하여 도커 이미지를 build&push 한 뒤 각 component에 맞는 image를 manifest에 명시해준다. component별로 Docker Image를 생성하고 개발 단계에서 마주하는 수많은 디버깅 작업으로 인해 수십번의 build&push가 필요한데 매번 CLI에서 명령어를 날리는 일이 여간 불편한게 아니다.

이번 포스팅에서는 이런 반복작업을 자동화 하기위해 Jenkins를 Github webhook으로 연결하여 git push시에 각 component에 필요한 이미지를 build 및 Docker hub에 push하는Jenkins pipeline을 구축 해보려고한다. 먼저 Jenkins에 대해 간단히 알아보자.


Jenkins란?

- https://www.jenkins.io/doc/

- 공식 문서에 따르면 Jenkins는 소프트웨어 구축, 테스트, 전달 또는 배포와 관련된 모든 종류의 작업을 자동화하는 데 사용할 수 있는 독립형 오픈 소스 자동화 서버라고 설명하고 있다. 간단히 말해 Java로 작성된 오픈 소스 CI 서버라고 할 수 있다.

Jenkins 특징

  • Jenkinsfile
    • Jenkinsfile 을 이용해 Job 혹은 파이프라인을 정의할 수 있으며 Jenkinsfile 덕분에 일반 소스코드를 다루는 Github 업로드, Vscode 로 수정하는 것으로 파일을 이용 가능하다.
  • Scripted Pipeline (스크립트 파이프라인)
    • Java와 유사한 Groovy라는 동적 객체 지향 프로그래밍 언어를 통해 관리되며 Jenkins 관련 구조를 자세히 가지지 않는다.
    • 유연하지만 Groovy라는 언어를 알아야 하기 때문에 시작하기 어렵다는 단점이 있다.
  • Declarative Pipeline (선언적 파이프라인)
    • 2016년 경 Cloudbees 에서 개발되었다. 사전에 정의된 구조만 사용할 수 있기 때문에 파이프라인이 단순한 경우에 적합하며 아직은 많은 제약사항이 따른다.
    • 아래 실습에서 선언적 파이프라인을 활용


Jenkins 설치(Docker)

- https://www.jenkins.io/doc/book/installing/

- 이 글에서의 Jenkins 설치는 docker가 설치 되어있는 리눅스 환경에서 진행한다.

- Docker 외에도 k8s, Linux, macOS, window 등에서 설치 가능(documentation 참조)

1. docker hub에서 jenkins 도커 이미지 가져오기

docker pull jenkins/jenkins:lts


2. 컨테이너 실행

docker run --name jenkins-docker -p 7979:8080 -p 50000:50000 -d -v /var/run/docker.sock:/var/run/docker.sock -v jenkins_home:/var/jenkins_home -u root jenkins/jenkins:lts
  • –name : container name
  • -p : [host port]:[container port]
  • -d : 백그라운드에서 컨테이너 실행 유지
  • -v : volume mount
  • -u : user


3. 컨테이너 접속 후 도커 설치

  • Jenkinsfile 에서 docker 명령어를 실행하기 위해서 컨테이너에 도커가 설치되어야 함
  • docker-compose 명령어도 사용 할 예정이니 같이 설치
docker exec -it jenkins-docker bash

curl https://get.docker.com/ > dockerinstall && chmod 777 dockerinstall && ./dockerinstall

apt install docker-compose


4. docker.sock 파일의 권한을 666으로 변경하여 그룹 내 다른 사용자도 접근 가능하게 변경

exit # 컨테이너 접속해제 후 진행

sudo chmod 666 /var/run/docker.sock


5. 초기 패스워드로 로그인

  • 초기 패스워드는 컨테이너의 log 혹은 컨테이너 /var/jenkins_home/secrets/initialAdminPassword 경로에서  복사
  • 7979 포트로 오픈된 Jenkins dashboard에 접속해서 초기 Administrator password를 입력하는 곳에 복사한 패스워드를 붙여넣기
docker logs jenkins-docker
*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required.
An admin user has been created and a password generated.
Please use the following password to proceed to installation:

94b73ef6578c4b4692a157f768b2cfef  # 패스워드 복사

This may also be found at:
/var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************


6. 초기 플러그인 설치

  • 왼쪽 Install suggested plugins 클릭
  • plugins이 설치가 안되는 경우 설치 재시도하면 정상 설치


7. 계정 생성

설치 및 로그인을 마치면 아래와 같은 Jenkins dashboard 화면이 나오게 된다.


credentials 생성

- docker hub에 이미지를 push하기 위해서는 docker 계정 정보를 담은 credentials를 추가해야 한다. credentials plugin은 Jenkins 초기설정에 suggested plugins으로 설치되었다.

[Dashboard]-[Jenkins 관리]-[Manage Credentials]-[Global credentials]-[Add Credentials] 클릭 후 아래 예시처럼 작성

  • Username: Docker hub 아이디
  • Password: docker hub access key
  • Docker access key(token)은 Docker hub [Account Settings]-[Security] 에서 New Access Token을 클릭해 얻을 수 있음


Jenkins Pipeline Job 생성

1. Pipeline 생성

Create a job -> ‘spaceship_pipeline’ 으로 이름입력 및 Pipeline 선택

2. Pipeline 설정

  • GitHub project: pipeline에 GitHub Repository로 이동할 수 있는 배너 생성
  • GitHub hook trigger for GITScm polling: GitHub webhook과 Jenkins를 연동하여 git push시에 pipeline 자동 빌드
  • Branches to build: master 브랜치로 변경이 일어났을때 jenkins가 반응


GitHub Webhook 설정

  • [Github Repository]-[Settings]-[Webhooks]-[Add webhook]
  • Payload URL : http://:/github-webhook/
  • Content type : application/json
  • Acitve 활성화


Jenkinsfile 작성

- Jenkinsfile은 위에서 언급한대로 declarative방식으로 작성하였고 코드가 수정된 component의 boolean 값을 true로 변경하게 되면 해당 component의 이미지가 build와 push되는 파이프라인이다.

def component = [
		Preprocess: false,
		Hyper: false,
		Train: false,
		Test: false,
		Bento: false
]

pipeline {
	agent any
	stages {
		stage("Checkout") {
			steps {
				checkout scm
			}
		}
		stage("Build") {
			steps {
                script {
					component.each{ entry ->
						stage ("${entry.key} Build"){
							if(entry.value){
								var = entry.key
								sh "docker-compose build ${var.toLowerCase()}"
							}	
						}
					}
				}
			}
		}
		stage("Tag and Push") {
			steps {
                script {
					component.each{ entry ->
						stage ("${entry.key} Push"){
							if(entry.value){
								var = entry.key
								withCredentials([[$class: 'UsernamePasswordMultiBinding',
								credentialsId: 'docker_credentials',
								usernameVariable: 'DOCKER_USER_ID',
								passwordVariable: 'DOCKER_USER_PASSWORD'
								]]){
								sh "docker tag spaceship_pipeline_${var.toLowerCase()}:latest ${DOCKER_USER_ID}/spaceship_pipeline_${var.toLowerCase()}:${BUILD_NUMBER}"
								sh "docker login -u ${DOCKER_USER_ID} -p ${DOCKER_USER_PASSWORD}"
								sh "docker push ${DOCKER_USER_ID}/spaceship_pipeline_${var.toLowerCase()}:${BUILD_NUMBER}"
								}
							}
						}
					}
				}
			}	


		}
	}
}
  • component 라는 이름의 리스트가 each 함수를 통해 반복문으로 실행되며 각 변수의 value값이 true인 경우 이미지 build와 Push가 일어나는 구조로 작성하였다.
  • Jenkinsfile 문법상 리스트의 각 변수 이름은 대문자로 시작되어야 하고 이미지 이름은 대문자가 들어가면 에러가 나기 때문에 toLowerCase() 함수를 통해 component의 각 key값들을 소문자로 바꿔주었다.
  • 위에서 생성한 credentials 정보를 통해 docker와 관련된 환경변수를 얻을 수 있으며 credentials에 등록된 계정의 docker hub에 이미지를 등록할 수 있게된다.
  • Jenkinsfile의 기본 환경변수인 BUILD_NUMBER라는 값을 이미지 태그값으로 활용하였다.


docker-compose.yml 작성

- docker-compose 명령어를 Jenkinsfile에서 사용하였으니 docker-compose.yml 파일을 작성해야 한다. Jenkins는 도커 이미지 빌드 자동화만 목적이기 때문에 최대한 간단하게 작성하였다.

version: "3"

services:
  preprocess:
    build: ./preprocess
    container_name: spaceship-preprocess
  hyper:
    build: ./hyper-tuning
    container_name: spaceship-hyper-tuning
  train:
    build: ./train
    container_name: spaceship-train
  test:
    build: ./test
    container_name: spaceship-test
  bento:
    build: ./bento
    container_name: spaceship-bento
  • build 항목에서 각 component의 Dockerfile의 경로를 적어준다.
  • 이미지의 이름을 명시하지 않으면 \_ 으로 이미지가 build 된다.


Git push 이후 이미지 bulild & push 자동화 확인

- 각 컴포넌트 코드를 Jenkinsfile, docker-compose.yml 파일과 함께 GitHub에 push 해보자. 

# CLI를 열고 프로젝트 루트경로로 이동
git add .
git commit -m "jenkins pipeline"
git push origin master

push가 완료되었다면 Gihub Webhook으로 인해 Jenkins pipeline이 자동으로 빌드된다. jenkins dashboaed를 확인해보자

  • 현재 Jenkinsfile의 component 리스트에서 Preprocess, Train의 value값이 true이기 때문에 preprocess와 train에 대한 작업을 수행하는 도커 이미지가 리눅스 서버에 build 되고 docker hub에 이미지가 등록되는 작업이 진행된다.
  • 실행된 빌드를 클릭한뒤 [console output] 탭에서 log를 확인해 볼 수 있다.


리눅스 서버에 이미지가 빌드되고 도커허브에 등록 되었는지 확인해보자.




End

지금까지 Jenkins를 활용해 도커 이미지를 자동으로 build & push하는 pipeline을 작성해보았다. 개발 초기단계나 수정이 필요할 때 각 컴포넌트별로 도커 이미지의 build & push 작업이 많이 반복되어 번거롭고 불편했는데 Jenkins pipeline을 통해 해결할 수 있었다. 

keep going

업데이트:

댓글남기기