VISION HONG
article thumbnail

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://<Server IP>:<Jenkins Port>/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의 경로를 적어준다.
  • 이미지의 이름을 명시하지 않으면 <Jenkins pipeline name>_<service name> 으로 이미지가 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

'MLOps' 카테고리의 다른 글

[ML Design Pattern] 0. Back to the basic  (0) 2022.09.06
Deploy with BentoML Yatai  (1) 2022.07.19
Understanding the Feature Store with Feast  (1) 2022.06.22
MLOps를 위한 Kubernetes  (0) 2022.01.05
DVC(Data Version Control) with Docker  (0) 2021.11.26
profile

VISION HONG

@Jeff Hong

깃허브 블로그로 이전했습니다. https://visionhong.github.io/