Introduction

  • 이전 글에서는 Jenkins로 배포를 제외한 부분을 완성하였다. 즉 CI/CD에서 CI까지 완성했다고 볼 수 있다.
  • 이번 글에서는 CD 부분을 구현할 것이다.

CD의 과정

  • 먼저 이전에 보았던 Jenkins Pipeline 스크립트를 보도록 하자.
pipeline {
    agent any

    tools {
        maven "M3" // Jenkins에서 설정한 Maven의 이름
    }

    stages {
        //...

        stage('Deploy') {
            steps {
                script {
                    // 원격 서버에서 애플리케이션 실행
                    sshagent(['deploy_ssh_key']) { // 'server-ssh-credentials'는 Jenkins에서 설정한 credentials ID
                        sh 'ssh -o StrictHostKeyChecking=no ubuntu@[server instance public ip]'
                        sh 'scp /var/jenkins_home/workspace/[jar file path] ubuntu@[server instance public ip]:/home/ubuntu/project/'
                        sh 'ssh -tt ubuntu@[server instance ip] sh ./deploy.sh'
                    }
                }
            }
        }
    }

    post {
        success {
            echo 'Deployment was successful.'
        }
        failure {
            echo 'Deployment failed.'
        }
    }
}
  • CD는 서버가 동작할 EC2 인스턴스에 CI과정을 통해 빌드된 jar파일을 전송하고, EC2 인스턴스에서 해당 jar파일을 실행하여 서버를 띄우는 동작을 수행한다.
  • 위의 스크립트 파일에서는 jar파일을 EC2 인스턴스에 전송하는 것 까지 수행하며 deploy.sh 파일을 실행하는 것을 통해 서버를 띄우는 동작을 수행한다.
  • 여기에서 사용되는 것은 SSH 프로토콜이다.

배포 설정

서버 EC2 인스턴스 생성 및 설정

  • 서버를 Jenkins가 있는 인스턴스에서 띄울 생각이 아니라면 다른 EC2 인스턴스에서 실행시켜야 한다. 다른 AWS EC2 인스턴스를 만들자.
  • 만드는 과정은 Jenkins EC2 Instance를 만든 것과 동일하다. 대신 인스턴스를 small이 아닌 micro로 지정했다는 것에 유의하라.
    • 만약 메모리가 부족할 것 같다면 해당 인스턴스에서도 swap memory를 설정해주도록 하자.
  • 서버 인스턴스도 생성된 후에는 보안 관련 설정을 해주어야 한다. Jenkins EC2 와는 SSH 프로토콜을 사용하여 통신하기 때문에 SSH 포트인 22번 포트가 열려있어야 하며 api 요청을 받을 수 있어야 하므로 8080포트, http 80, https 442포트를 모두 열도록 보안 그룹을 설정하도록 하자.
  • 기억이 나지 않는다면 Jenkins jenkins로 CI/CD 구축하기 - (1)을 참고하라
  • 이후에 해당 인스턴스로 접속해 보자.
  • 지금 단계 이후로 Jenkins 인스턴스의 경우 Jenkins 인스턴스가 설치되어 있는 인스턴스로, 서버 인스턴스의 경우 우리가 배포할 곳의 인스턴스를 의미하는 것으로 가정하고 이야기할 것이다.

SSH 통신 설정

  • SSH 통신을 하기 위해서는 공개 키 - 개인 키로 대표되는 비대칭 키가 필요하다. Jenkins 인스턴스에 접속하자.

  • Jenkins는 Docker Conatiner에서 돌아가고 있으므로 Jenkins Container에 들어가야 할 필요가 있다. 그곳에서 비대칭 키를 생성할 것이다.
sudo docker exec -it jenkins bash
  • 다음 명령어를 통해 jenkins container bash에 접속할 수 있다.
  • 여기에서 SSH key를 만들 것이다.
ssh-keygen -t rsa -b 4096
  • 해당 명령어를 사용하면 4096 길이의 rsa로 생성된 공개 키와 개인 키 쌍이 생성된다.

  • 생성하기 전에 여러 조건들을 물어보는데 그냥 모두 엔터로 스킵해주면 된다.
  • 위의 경로들에서 생성된 공개 키와 개인 키를 확인할 수 있다. 여기서 우린 공개 키가 필요하다. 공개 키를 한 번 보도록 하자.
cat ~/.ssh/id_rsa.pub
  • 다음 명령어를 통해 공개 키를 확인할 수 있다.
  • 이후 나온 결과를 처음부터 끝까지 모두 복사한다. 이후 서버 인스턴스로 다시 넘어간다.
sudo vi ~/.ssh/authorized_keys
  • 서버 인스턴스에서 해당 명령어를 치면 authorized_keys가 vi 에디터로 열리게 된다. 이때 ~가 어딘지가 중요한데 EC2면 ubuntu로 지정되어 있기에 상관없지만 다른 경우 ubuntu로 이동해서 열거나 해당 디렉토리에서 넣고 이후 Jenkins Pipeline에서 스크립트를 수정해야 한다는 것을 유의하라
  • sudo를 넣지 않을 시 수정한 뒤에 저장할 때 권한 문제가 발생할 수 있으므로 넣어주도록 하자.

  • vi에서 i를 누르면 insert 모드로 들어가게 되는데 insert 모드로 들어간 후에 아까 복사했던 공개 키를 붙여넣어주자.
  • 이후 ESC를 누르면 다시 command mode로 들어가게 되는데 이때 :wq를 누르면 저장 후 종료를 하게 된다.
  • 이후 Jenkins instance에서 ssh 통신을 해봐 정상적으로 진행되는지를 확인한다.
ssh -o StrictHostKeyChecking=no ubuntu@[server instance ip]
  • 해당 명령어로 서버 인스턴스와 Jenkins 인스턴스가 SSH로 통신이 가능한지 확인한다.

  • 성공한다면 위와 같이 server-instance로 접속하는 것을 확인할 수 있다.
  • 만약 실패한다면 여러 이유가 있는데 가장 유력한 것은 server-instance에서 붙여넣은 public key가 정상적으로 복사 붙여넣기가 되지 않은 경우이다.

 

Jenkins ssh-agent 설정

  • 통신이 되는 것을 확인했다면 다시 Jenkins로 돌아가자. Jenkins 관리에서 Credentials로 들어간다.

  • 여기에는 우리가 만든 public key와 private key 중 private key를 저장할 것이다. global을 누른 후 Add Credentials로 들어간다.

  • 우리는 SSH 통신에 사용할 것이기 때문에 SSH Username with private key로 분류를 지정할 것이다.
  • ID의 경우에는 위의 sshagent(['deploy_ssh_key'])에 있는 key를 의미하며 여기에서는 deploy_ssh_key라고 적으면 된다. 만약 다르게 적을 경우에는 위의 Pipline 스크립트를 수정해야 하는 것을 유의하라.
  • username은 접속할 유저 이름인데 EC2에는 default로 ubuntu로 되어 있기 때문에 ubuntu로 해놓으면 된다.

  • private key는 Jenkins 인스턴스에서 다음의 명령어로 확인할 수 있다.
cat ~/.ssh/id_rsa
  • 주의할 점은 -----BEGIN OPENSSH PRIVATE KEY----- 와 -----END OPENSSH PRIVATE KEY-----까지 모두 포함해서 복사 붙여넣기를 해야 한다는 것이다. 이후 Create를 누르면 정상적으로 등록되는 것을 확인할 수 있다.

 

Deploy.sh 작성

  • 스크립트를 보면 'ssh -tt ubuntu@[server-instance-ip] sh ./deploy.sh' 부분이 보일 것이다.
  • 이 deploy.sh 파일이 server 인스턴스에서 jar파일을 실행하는 역할을 수행한다. 내용은 아래와 같다.
pid=$(pgrep java)
echo current spring pid is ${pid}

if [ -n "${pid}" ]
then
        sudo kill -9 ${pid}
        echo kill process ${pid}
        sleep 5
else
        echo no process
fi

echo "Deployment Start..."

JAR_PATH=$(ls -t /home/ubuntu/project/*.jar | head -1)
sudo chmod +x ${JAR_PATH}
sudo nohup sudo java -jar ${JAR_PATH} >> /home/ubuntu/project/application.log &

sleep 5

echo "Done"
  • 이 쉘 스크립트는 이미 jar파일이 실행되고 있는경우, 즉 이미 돌고 있는 서버가 존재한다면 해당 서버를 종료시킨다. 이후 새로 복사되어 온 jar 파일을 실행 가능하게 한 뒤에 다시 백그라운드에서 실행하도록 하는 코드이다.
  • 해당 쉘 스크립트를 server 인스턴스의 /home/ubuntu/에 만들도록 하자.
sudo vi deploy.sh
  • 해당 명령어를 실행시키고 i를 눌러 insert mode로 전환한 다음 위의 쉘 스크립트를 입력하고 ESC를 눌러 command mode로 다시 진입한다. 이후 :wq를 통해 저장한다.
  • 해당 파일은 실행 권한이 없기 때문에 chmod를 통해 실행 가능하도록 해준다.
sudo chmod 700 deploy.sh

 

 

서버 인스턴스 IP 및 나머지 설정

  • 이제 위의 Jenkins Pipeline에서 server instance ip라고 되어 있는 부분들을 모두 서버 인스턴스 ip로 바꿔 넣어주자. 대괄호도 모두 지워줘야 한다는 것을 유의하라.
  • 서버 인스턴스에서 /home/ubuntu로 들어가면 project 폴더가 없음을 확인할 수 있는데 pipeline script를 수정하거나, mkdir 명령어를 통해 project 폴더를 생성해 주자.
  • 여기까지 진행하면 Deploy 및 전체 Jenkins Pipeline 설정이 완료된다.

 

테스트

  • 모든 스크립트와 준비가 마무리되었다. 지금 빌드를 눌러서 전체 과정이 잘 돌아가는지 확인하자.

  • 필자는 AWS EC2에서 진행하다가 Vultr로 옮겼는데 Vultr의 경우 linux os 수준에서도 방화벽을 열어줘야 하는 문제가 있어 몇번 실패했었다. 아마 AWS EC2는 별다른 조치 없이도 모두 잘 따라했다면 잘 실행될 것이라고 생각된다.

 

디버깅

  • 간혹 deploy.sh에서 몇몇 구간에 sudo를 사용함으로 인해 sudo 비밀번호를 입력해라는 요청이 올 때가 있다. jenkins에서 비밀번호를 입력하기에는 곤란하기 때문에 이를 해결할 방법을 소개한다.
  • 먼저 서버 인스턴스로 접속한다. 이후에 슈퍼유저 계정으로 넘어간다.
sudo su -
  • root로 변경된 이후에 다음과 같은 명령어를 실행시킨다.
visudo

  • 아마 이런 파일이 열리면서 수정이 될 것이다. 만약 수정이 안된다면 i키를 눌러 insert mode로 진행시키면 된다. 여기서 아래로 쭉 내려주자.

  • %sudo 라고 적힌 곳 아래쪽에 다음을 입력한다.
ubuntu ALL=(ALL) NOPASSWD: ALL
  • 이후 저장한다. 저장의 경우 ESC 이후 :wq를 누르거나 위와 같이 되어 있는 경우 Ctrl + X를 눌러 나가면 저장할 것이냐고 묻는데 y를 입력한 후 엔터를 눌러 저장해주면 된다.
  • 이러면 리눅스 시스템에 ubuntu 라는 유저는 패스워드 없이 sudo를 사용할 수 있다는 것으로 설정되므로 비밀번호를 물어 진행이 되지 않는 상황이 해결된다.

 

마치며

  • 여기까지가 CI/CD 파이프라인을 만드는 과정이었다. 이후에는 깃허브에 push되는 것을 트리거로 삼아 Jenkins Pipeline이 동작하는 방법에 대해 알아볼 것이다.
복사했습니다!