반응형

이번 포스트는 앱 배포에 관한 것으로, docker 환경에 구축된 jenkins를 이용해서, git repository 에 저장된 소스를 가지고 와서 docker 이미지를 빌드하고, private docker registry 에 배포하는 과정에 대해서 정리해 보았습니다.

 

 

서버 환경

먼저 서버를 준비해야 하는데, 어차피 docker로 배포가 될 예정이기 때문에, 테스트 환경은 Virtual Box 에 설치한 Ubuntu 22.04 LTS 상에서 docker를 설치해서 진행했습니다.

 

 

Docker 설치

대부분 application은 docker 상에서 구동될 예정이기 때문에 먼저 docker를 설치합니다.

 

설치과정은 다음 링크에 자세히 나와 있습니다.

Install Docker Engine on Ubuntu | Docker Documentation

 

1. 기존에 설치된 버전을 삭제합니다.

sudo apt-get remove docker docker-engine docker.io containerd runc

 

2. apt 패키지 인덱스를 업데이트 하고 필요한 패키지를 설치합니다.

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release

 

3. Docker의 GPG Key를 추가합니다.

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

 

4. Docker 설치를 위한 apt repository를 세팅합니다.

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

 

5. apt 패키지를 다시 업데이트 하고, docker를 설치합니다.

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

 

Docker Registry

docker registry는 docker 이미지를 빌드한 후에 배포하기 위해서 사용합니다. docker.io 등에서 제공하는 public registry를 사용하는 경우에는 이 과정을 생략해도 좋습니다.

 

docker-compose.yml 파일을 생성해서 다음과 같이 작성합니다.

version: '3'

services:
  registry:
    container_name: docker_registry
    image: registry:2
    restart: unless-stopped
    ports:
    - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./data:/data

 

docker-compose 명령어 혹은 docker 명령어로 registry 서버를 시작합니다.

 

docker-compose 가 설치되어 있는 경우

docker-compose up -d

 

docker 명령어를 사용하는 경우

docker compose up -d

 

docker registry 의 경우 https를 사용하는 것이 기본이지만.. 테스트 환경 혹은 private 네트워크 환경에서는 굳이 https를 사용할 필요가 없어서 http를 사용할 경우에는, 아래와 같이 설정을 해 줍니다.

 

1. /etc/docker/daemon.json 파일 오픈 (없으면 생성)

2. 아래와 같은 내용을 추가합니다.

{"insecure-registries": ["host:port"]}

3. docker 서비스를 재시작 합니다.

sudo systemctl restart docker

 

혹시 기본 옵션 파일을 인식하지 못한다면, /etc/default/docker 파일에 DOCKER_OPTS="--config-file=/etc/docker/daemon.json" 을 추가하고, docker 서비스를 재시작 합니다.

 

참고 링크

https://stackoverflow.com/questions/49674004/docker-repository-server-gave-http-response-to-https-client

 

 

Jenkins 시작

Jenkins도 별도로 설치하지 않고, docker 컨테이너 형태로 사용을 할 예정인데요, 이런 식으로 사용하는 경우 docker 관련 플러그인을 사용할 때, docker 명령어를 찾지 못하거나 glibc 버전 문제가 발생할 수 있어서, jenkins docker 이미지에  docker를 추가로 설치해서 사용하도록 하겠습니다.

 

먼저 아래와 같이 Dockerfile 파일을 준비합니다. 

FROM jenkins/jenkins:lts
USER root
RUN apt-get update -qq \
    && apt-get install -qqy apt-transport-https ca-certificates curl gnupg2 software-properties-common
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
RUN add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"
RUN apt-get update  -qq \
    && apt-get -y install docker-ce
RUN usermod -aG docker jenkins

 

아래 명령어를 이용해서 이미지를 빌드합니다.

docker image build -t custom-jenkins-docker .

 

Jenkins를 띄울 때도, docker-compose.yml 을 작성해서 띄우도록 하겠습니다.

docker-compose.yml 파일을 다음과 같이 작성합니다.

version: '3'

services:
  jenkins:
    container_name: jenkins
    image: custom-jenkins-docker:latest
    restart: unless-stopped
    privileged: true
    user: root
    ports:
     - "8080:8080"
     - "50000:50000"
    volumes:
     - ./data:/var/jenkins_home
     - /var/run/docker.sock:/var/run/docker.sock

 

docker-compose 명령어 혹은 docker 명령어로 jenkins 서버를 시작합니다.

 

docker-compose 가 설치되어 있는 경우

docker-compose up -d

 

docker 명령어를 사용하는 경우

docker compose up -d

 

 

Jenkins 설정

웹 브라우저를 이용해서 http://localhost:8080 으로 접속합니다. 참고로 저는 virtual box의 host 네트워크 설정으로 host PC에서 IP (192.168.56.101) 로 접근할 수 있게 설정하였습니다.

 

처음 실행시 아래와 같은 화면이 나옵니다.

 

화면에 나온 설명대로 docker 컨테이너 내에서 /var/jenkins_home/secrets/initialAdminPassword 파일을 열어보면 초기 비밀번호를 확인할 수 있습니다. 해당 파일은 docker-compose 로 띄울 때, .data 폴더를 /var/jenkins_home 폴더로 매핑시켜 두었기 때문에,  Ubuntu 서버 내에서 .data/secrets/initialAdminPassword 파일을 열어보아도 동일한 결과를 얻을 수 있습니다.

그 외에는 docker 컨테이너 log를 이용해서 아래와 같이 초기 비밀번호를 확인할 수도 있습니다.

docker logs jenkins

 

다음 화면에서는 Install suggested plugins를 클릭해서 기본 플러그인들을 설치합니다.

 

다음 화면에서는 Admin User를 생성해도 되지만 귀찮으시면 Skip 을 눌러서 넘길 수도 있습니다. (Skip시 initial password를 계속 사용하게 됩니다.)

 

Jenkins URL을 세팅하고, 시작합니다. 

 

 

Jenkins 화면에서 Jenkins 관리 > 플러그인 관리 > Available plugins 를 차례로 클릭합니다.

 

검색창에서 docker를 입력합니다.

 

Docker, Docker Commons, Docker Pipeline, Docker API, docker-build-step 를 체크하고, Download now and install after restart 를 클릭합니다.

 

 

설치 후 Jenkins 재시작을 체크하면, Jenkins가 재시작 됩니다.

 

마지막으로 docker socket을 세팅해 줍니다.

Jenkins 관리 > 시스템 설정을 차례로 클릭합니다.

스크롤해서 Docker Builder 항목을 찾아가서 Docker server REST API URL에 아래와 같이 "unix:///var/run/docker.sock" 을 입력합니다.

 

 

빌드 테스트

이제 Jenkins 에 Pipeline 아이템을 추가해서 빌드 테스트를 진행 해보겠습니다.

 

Jenkins Dashboard에서 새로운 Item을 클릭합니다.

 

적당한 이름을 입력하고, Pipeline 을 선택하고 OK 버튼을 눌러서 넘어갑니다.

 

먼저, Docker 이미지에 버전을 태깅하기 위해서 BUILD_NUMBER를 인자로 받도록 합니다.

 

General 섹션에서 "이 빌드는 매개변수가 있습니다" 를 체크하고, String Parameter를 선택하고, 매개변수 명에 "BUILD_NUMBER" 라고 적습니다.

 

쭉 아래로 내려서 Pipeline 섹션에 빌드 스크립트를 작성합니다.

아래와 비슷하게 작성하면 됩니다.

 

 

스크립트 내용은 아래와 같습니다.

node {
    def app
    def imgName = "customapps/django_test"
    stage('Clone Repository') {
        git 'https://github.com/shineum/django_sample.git'
    }
    stage('Build Image') {
        app = docker.build("${imgName}:${env.BUILD_NUMBER}")
    }
    stage('Push Image') {
        docker.withRegistry('http://192.168.56.101:5000') {
            app.push("${env.BUILD_NUMBER}")
            app.push("latest")
        }
    }
    stage('Clean Up') {
        sh "docker images ${imgName} -q | xargs docker rmi -f > /dev/null 2>&1 || true"
    }    
}

 

스크립트 내용은 대략 다음과 같습니다.

 

1. Clone Repository

github에서 소스 코드를 가지고 옵니다.

 

2. Build Image

docker 이미지를 빌드합니다. Jenkins 서버가 아니라 docker를 설치한 Ubuntu 서버에 이미지가 생성됩니다.

 

3. Push Image

해당 docker 이미지를 private registry 서버에 push 합니다. (registry 서버는 virtual box 에서 host 전용 네트워크를 이용해서 ip를 설정하였습니다.)

처음에는 빌드 버전대로 하나 올리고, 새 빌드를 latest 로 세팅합니다.

 

4. Clean Up

Ubuntu 서버에는 앱 이미지가 필요 없기 때문에 불필요한 이미지들을 정리합니다. 이 때, 앱을 빌드할 때 사용되는 base 이미지들을 같이 삭제하게 되면, 빌드 시간이 길어지기 때문에, 앱 배포 이미지만 삭제합니다. 

 

스크립트 작성이 끝났으면 저장하고 빠져나옵니다.

 

 

파라미터와 함께 빌드를 클릭하고, BUILD_NUMBER를 입력합니다.

 

이미지 빌드는 base image를 다운로드해야 하며, 앱에서 필요로 하는 모듈도 다운로드해야 하기 때문에 시간이 조금 걸립니다.

 

빌드가 완료되면 아래와 비슷한 결과가 출력됩니다.

 

확인을 위해 registry 서버에 접속해보면,

 

먼저 카탈로그 확인

http://192.168.56.101:5000/v2/_catalog

 

그리고, 아래와 같이 빌드 관련 정보를 확인할 수 있습니다.

http://192.168.56.101:5000/v2/customapps/django_test/tags/list

다음 명령으로 릴리즈 된 docker 이미지를 registry 서버에서 pull 할 수 있습니다. (latest 대신 버전을 적어줘도 됩니다.)

docker pull 192.168.56.101:5000/customapps/django_test:latest

 

 

빌드 실행

마지막으로 조금 전에 빌드한 이미지를 앱 서버에 배포해서 실행해 보겠습니다.

실제 운영 환경에서는 kubernetes 나 docker swarm 등을 사용하기 때문에 조금 더 복잡하겠지만.. 여기서는 단순하게 Ubuntu 서버에서 docker 이미지를 실행해 보겠습니다.

 

Jenkins Dashboard에서 새로운 Item을 클릭합니다. 이번에도 적당한 이름을 지정하고 Pipeline을 선택합니다.

 

General 섹션에서 "이 빌드는 매개변수가 있습니다" 를 체크하고, String Parameter를 선택하고, 매개변수 명에 "BUILD_NUMBER" 라고 적습니다. 그리고 이번에는 Default Value 에 "latest" 를 적어 줍니다.

 

쭉 아래로 내려서 Pipeline 섹션에 빌드 스크립트를 작성합니다.

아래와 비슷하게 작성하면 됩니다.

 

 

스크립트 내용은 아래와 같습니다.

node {
    def containerName = "cn_django_test"
    def imgName = "customapps/django_test"
    def regServer = "192.168.56.101:5000"
    stage("Stop App") {
        sh "docker stop ${containerName} || true && docker rm ${containerName} || true"
    }
    stage("Clean Up") {
        sh "docker images ${imgName} -q | xargs docker rmi -f > /dev/null 2>&1 || true"
    }
    stage("Start App") {
        sh "docker run -it -d -p 8000:8000 --name ${containerName} ${regServer}/${imgName}:${env.BUILD_NUMBER}"
    }
}

 

스크립트 내용은 대략 다음과 같습니다.

 

1. Stop App

기존에 실행 중인 앱을 정지 합니다.

 

2. Clean Up

기존에 실행 할 때 사용했던 app build 이미지를 제거 합니다.

 

3. Start App

지정한 버전의 app build 이미지를 registry로부터 pull하고 app을 구동합니다.

 

 

스크립트 작성이 끝났으면 저장하고 빠져나옵니다.

 

 

파라미터와 함께 빌드를 클릭하고, BUILD_NUMBER는 "latest"로 두고 실행합니다.

 

빌드가 잘 완료되었는지 확인하고,

 

App URL로 접속해보면, django 앱이 실행되고 있음을 확인할 수 있습니다.

 

 

이상으로 포스트를 마치도록 하겠습니다. 

반응형

+ Recent posts