[젠킨스 구축] 7. 자동 배포, 롤백 - f-lab-edu/jshop GitHub Wiki

소스코드를 테스트해 NCR에 push 하는 파이프라인까지 구성을 완료하였다.

마지막으로 생성한 image를 운영중인 서버에 배포하고, 만약 문제가 생기면 롤백을 하는 파이프라인을 하는 과정이다..

docker swarm

운영중인 docker 컨테이너를 새 버전으로 업데이트하고, 문제가 생기면 롤백할 수 있는 방법이 필요했다. 즉 컨테이너 오케스트레이션이 필요했다.

가장 유명한 방법으로는 쿠버네티스가 있지만 현재 프로젝트에서 쿠버네티스까지 사용할정도는 아니라고 생각했다.

도커 서버간의 클러스터만 간단하게 구성할정도만 있으면 된다고 생각해 docker swarm 을 사용하기로 했다.

docker swarm을 사용한다면 서비스의 확장, 롤링 업데이트, 롤백또한 가능했다.

여기선 docker swarm 에 대해선 다루지 않는다.

아래는 간단하게 서비스를 띄우고, 업데이트와 롤백에 대한 설정 정의한 compose 파일이다.

version: '3.8'

services:
  jshop:
    image: jshop.kr.ncr.ntruss.com/jshop:v1.0.0
    ports:
      - "8080:8080"
    deploy:
      replicas: 3
      rollback_config:
        parallelism: 1
      update_config:
        parallelism: 1
        failure_action: rollback

아래 명령으로 compose를 swarm 클러스터 위에 올린다.

docker stack deploy -c docker-compose.yml jshop

이렇게 되면 적절한 스케줄링으로 컨테이너가 swarm 클러스터 내부에 알아서 생성된다.

위의 compose 설정으로는 3개의 컨테이너가 생성되므로, 3개의 컨테이너가 swarm 클러스터 내부에 적당히 생성되게 된다. k8s의 deployment와 유사하다

업데이트

생성한 서비스 이미지의 업데이트는 다음 명령으로 수행할 수 있다.

docker service update --image {new image} jshop_jshop

docker-compose의 deploy 설정대로 만약 업데이트에서 문제가 생기면 롤백하게 될것이다.

젠킨스 파이프라인 설정

이제 젠킨스에서 docker swarm 마스터서버로 명령을 날리는 방법에 대해 알아본다.

플러그인 설치

명령을 날리기 위해 SSH Agent Plugin 플러그인을 사용했다.

파이프라인에서 ssh 명령을 날릴 수 있는 플러그인이다.

Credentials 설정

젠킨스 공개키 저장

명령을 날리기 위해선 호스트에 ssh 접속을 할 수 있어야 한다.

ssh-key로 ssh 접속을 하기 위해선 타겟 호스트에 소스 호스트의 ssh 공개키가 저장되어 있어야 한다.

타겟 (docker swarm 마스터 서버) 에 젠킨스 ssh 공개키를 저장한다.

vi ~/.ssh/authorized_keys

해당 파일에 공개키를 넣기만 하면 된다.

젠킨스 Credentials 생성.

접속을 위해 젠킨스에서 Credentials 를 생성한다.

서버에 넣은 공개키에 대응되는 개인키로 만들면 된다.

젠킨스 관리 -> Credentials 에서 새로운 Credentials 를 만든다.

SSH Username with private key 로 만들며, ID에는 사용할 키의 ID {아무거나}, username 에는 접속할 호스트의 유저네임, private key에는 젠킨스의 개인키를 등록한다.

이제 인증 작업은 끝났다.

파이프라인 구축

배포를 위한 파이프라인은 다음과 같다.

pipeline {
    agent any
    
    environment {
        NCLOUD_ACCESS_KEY_ID = credentials('NCLOUD_ACCESS_KEY_ID')
        NCLOUD_SECRET_KEY = credentials('NCLOUD_SECRET_KEY')
        IMAGE = 'jshop.kr.ncr.ntruss.com/jshop:v1.0.1'
    }

    stages {
        stage('Deploy') {
            steps {
                script {
                    def SERVERS = ["master_server"]
                    
                    SERVERS.each { server ->
                        sshagent(['ssh-deploy-key']) {
                            sh "ssh jhkim@${server} 'echo ${NCLOUD_SECRET_KEY} | docker login jshop.kr.ncr.ntruss.com -u ${NCLOUD_ACCESS_KEY_ID} --password-stdin'"
                            sh "ssh jhkim@${server} 'docker service update --image ${IMAGE} jshop_jshop --with-registry-auth'"
                        }
                    }    
                    
                    
                }
            }
        }
    }
}

명령을 수행할 서버가 하나 이상일 수 있기 때문에 반복문으로 로직을 짰다.

반복문 내부에서 설명할 점이라면 sshagent() 에는 위에서 만든 Credentials의 ID를 넣어준다.

배포를 진행할 서버에 접속해 NCR 서버의 docker login 을 수행하고, 새로 배포된 이미지로 업데이트를 진행한다.

정리

NCR에 업로드된 새 이미지를 업데이트 하는 방법에 대해서 고민해봤다.

여러 방법을 고민해보다 결국 답은 컨테이너 오케스트레이션밖에 없다고 생각했다.

하지만 쿠버네티스는 너무 무거울것 같았고, docker 에 내장된 swarm 을 사용해 클러스터를 구축해 오케스트레이션을 사용하기로 했다.

swarm은 생각보다 쉽게 구축할 수 있었고, 기능도 딱 내가 필요한 만큼만 있어서 너무 적당했다.

service 단위 관리, 롤링 업데이트, 문제 발생시 롤백등 나에게 필요한 기능만 있어서 아주 적절했다.

또한 마스터 노드에 ssh로 업데이트 명령을 보내기만 한다면 서비스의 모든 이미지가 업데이트 되기 때문에 관리하기도 편했다.

여기까지 진행한 Jenkinsfile 은 아래와 같다.

pipeline {
    agent any
    
    environment {        
        NCLOUD_ACCESS_KEY_ID = credentials('NCLOUD_ACCESS_KEY_ID')
        NCLOUD_SECRET_KEY = credentials('NCLOUD_SECRET_KEY')
        IMAGE = 'jshop.kr.ncr.ntruss.com/jshop:v1.0.1'
    }

    stages {
        stage('Test') {
            steps {
                script {
                    dir('be') {                
                       echo 'Test'
                        sh './gradlew clean test'
                    }
                }
            }
        }

        stage('Build') {
            steps {
                script {
                    dir('be') {
                        echo 'build'
                        sh "docker build -t ${IMAGE} ."
                    }                    
                }
            }
        }

        stage('NCR_Push') {
            steps {
                script {
                    echo 'push'
                    sh "echo ${NCLOUD_SECRET_KEY} | docker login jshop.kr.ncr.ntruss.com -u ${NCLOUD_ACCESS_KEY_ID} --password-stdin"                
                    sh "docker push ${IMAGE}"
                }
            }
        }

        stage('Deploy') {
            steps {
                script {
                    def SERVERS = [:]
                    configFileProvider([configFile(fileId: 'a05b0d1b-420b-4e46-a827-9b7bae9b7e25', variable: 'JSON_CONFIG_FILE')]) {
                        def jsonContent = readFile(file: JSON_CONFIG_FILE)
                                            
                        def jsonConfig = new groovy.json.JsonSlurper().parseText(jsonContent)
                        SERVERS = jsonConfig.servers
                    }
                    
                    SERVERS.each { server ->
                        sshagent(['ssh-deploy-key']) {
                            sh "ssh jhkim@${server} 'echo $NCLOUD_SECRET_KEY | docker login jshop.kr.ncr.ntruss.com -u ${NCLOUD_ACCESS_KEY_ID} --password-stdin'"
                            sh "ssh jhkim@${server} 'docker service update --image ${IMAGE} jshop_jshop --with-registry-auth'"
                        }
                    }                                            
                }
            }
        }
    }
}