sudo apt update
sudo apt upgrade
sudo apt install build-essential
$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
$ sudo wget -qO- https://get.docker.com/ | sh
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo usermod -aG docker ${USER}
$ sudo systemctl restart docker
$ docker -v
$ docker pull jenkins/jenkins:lts
$ docker images
$ docker run -d -p 8080:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /home/ubuntu/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -u root jenkins/jenkins:lts
$ docker ps
프리티어 EC2는 너무 작고 소중합니다. Jenkins를 구성하고 빌드하면 멈춰버립니다. AWS CloudWatch로 사용률을 보면 아주 그래프를 뛰쳐 나갑니다. 그래서 사전에 swap 메모리를 할당해서 이러한 문제를 방지한다.
프리티어 EC2 기본 RAM이 1GB이니, 2GB(128MB * 16) swap 파일을 만들어 할당합니다.
$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ sudo swapon -s
$ sudo vi /etc/fstab
/swapfile swap swap defaults 0 0
/var/lib/jenkins/secrets/initialAdminPassword를 확인해야 하는데 Jenkins Container에 접속하여 얻어오거나 "docker logs jenkins" 명령어를 사용하면 된다.
// Container에 접속하지 않고 확인
$ docker logs jenkins
// Container 접속
$ docker exec -it jenkins bash
// 암호 파일 확인
$ sudo cat /var/lib/jenkins/secrets/initialAdminPassword
Jenkins 메인 대시보드 페이지가 나오면 성공
Jenkins Container를 생성할 때 "/home/ubuntu/.ssh:/root/.ssh"로 .ssh 디텍도리를 마운트 해놓았기 때문에 Container 밖에서 ssh 키를 생성하면 Jenkins Container와 연결된다.
// 그냥 전부 enter를 입력해 default로 만든다.
$ ssh-keygen
EC2에 접속하면 기본 유저가 ubuntu이기 때문에 /home/ubuntu/.ssh에 id_rsa와 id_rsa.pub이 생성된다.
만들어 놓은 Github Repository > Settings > Deploy Keys > Add deploy key로 접속한다.
Title은 Jenkins로 지어 주고(마음대로 해도 된다), Key 부분에 id_rsa.pub에 들어있는 public key 값을 넣어준다. 아래 명령어로 확인 가능하다.
$ cd /home/ubuntu/.ssh
$ cat id_rsa.pub
Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.
$ cd /home/ubuntu/.ssh
$ cat id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
...
... 이와 같은 형태의 key가 private key 입니다 ...
...
-----END OPENSSH PRIVATE KEY-----
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Docker 검색 > Docker, Docker Pipeline 플러그인 설치 및 재실행
Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.
Jenkins Pipeline에서 Docker 명령어를 사용할 수 있도록 Jenkins Container 내부에 Docker를 설치해야 한다.
$ docker exec -it jenkins bash
$ docker run -d -p 8080:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /home/ubuntu/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -u root jenkins/jenkins:lts
Jenkins Container를 가동할 때 위에 처럼 외부 Docker volume을 연결해놓아서 아래처럼 docker.sock 권한 변경만 하면 Container 내부에서 docker 명령어가 사용 가능하다고 하는데 Pipeline Script를 작성하고 Build했더니 docker 명령어 사용 부분에서 command not found
에러가 발생하여 그냥 Container 내부에도 Docker를 설치하였다.
Docker 설치 방법은 위에서 EC2 내에 Docker를 설치한 방법과 동일하다. 다만 Container 내부에 sudo, vi, wget이 설치되어 있지 않아 차례대로 설치 후 Docker를 설치해야 한다.
$ sudo chmod 666 /var/run/docker.sock
Jenkins로 Gradle 빌드하고 Dockerfile로 도커 이미지를 빌드해서 Docker Hub에 Push하고 Spring Boot Server에서 도커 이미지를 Pull해서 실행하게 하기 위해 필요하다. Jenkins Pipeline Script에서 SSH를 사용하여 Spring Boot Server의 명령어 실행을 할 수 있도로 하는 것이다.
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > SSH Agent 플러그인을 검색하고 설치 및 재실행
// Jenkins Server에서 public key 확인
$ cat /home/ubuntu/.ssh/id_rsa.pub
// Spring Boot Server에 Jenkins Server public key 추가
$ vi /home/ubuntu/.ssh/authorized_keys
Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.
$ cd /home/ubuntu/.ssh
$ cat id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
...
... 이와 같은 형태의 key가 private key 입니다 ...
...
-----END OPENSSH PRIVATE KEY-----
전에 Jenkins Pipeline이 아닌 Freestyle project로 동일한 CI/CD를 구성했을 때는 SSH Agent Plugin이 아닌 Publish Over SSH Plugin을 사용했다. 하지만 Pipeline으로 변경하면서 Script에서 SSH를 사용하는 방법으로 SSH Agent Plugin이 더 많이 검색되고 간단해보여 SSH Agent Plugin을 사용하였다. 하지만 두 개의 플러그인의 차이를 나중에 보다 자세하게 알아보아야겠다.
Freestyle project로 구성했을 때 Publish Over SSH를 설치 및 설정했던 방법은 아래와 같다.
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Publish Over SSH 플러그인을 검색하고 설치 및 재실행
Jenkins 대시보드 > Jenkins 관리 > 시스템 설정에서 Publish Over SSH 영역의 고급 버튼을 눌러 설정
즉 /home/ubuntu/.ssh/id_rsa를 입력하면 안된다.
우리는 다행이 Jenkins Server에서 컨테이너를 실행할 때 /home/ubuntu/.ssh를 /root/.ssh와 연결해 놓았기 때문에 /root/.ssh/id_rsa를 입력하면 된다.
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Slack Notification 플러그인을 검색하고 설치 및 재실행
Slack 앱에서 하단의 앱 추가 > jenkins 검색 > jenkins 선택 후 연동을 하면 연동 가이드 웹페이지에 하위 도메인과 토큰이 출력된다. 이를 이용하여 Slack Credentials을 등록한다.
Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.
Jenkins 대시보드 > Jenkins 관리 > 시스템 설정에서 Slack 영역을 설정
이제 드디어 Pipeline을 구성할 준비가 됐다! Jenkins 대시보드 > 새로운 Item에서 item name을 입력하고 Pipeline을 선택, OK 버튼을 누른다.
Github Repository에 push event가 발생했을 때 자동으로 Build가 실행되게 하기 위해 Pipeline과 Github Webhook을 연동해야 한다.
Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Github Integration 플러그인을 검색하고 설치 및 재실행
Github Repository에서 Settings > Webhooks > Add Webhook 을 눌러 Webhook을 추가한다.
pipeline {
agent any
environment {
imagename = "docker build로 만들 이미지 이름"
registryCredential = 'Docker Hub Credential ID'
dockerImage = ''
}
stages {
stage('Prepare') {
steps {
echo 'Clonning Repository'
git url: 'Github Repository SSH Url(git@github.com로 시작)',
branch: 'Clone 받아올 Branch 이름',
credentialsId: 'Github Credential ID -> github'
}
post {
success {
echo 'Successfully Cloned Repository'
}
failure {
error 'This pipeline stops here...'
}
}
}
stage('Bulid Gradle') {
steps {
echo 'Bulid Gradle'
dir('.'){
sh './gradlew clean build'
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
stage('Bulid Docker') {
steps {
echo 'Bulid Docker'
script {
dockerImage = docker.build imagename
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
stage('Push Docker') {
steps {
echo 'Push Docker'
script {
docker.withRegistry( '', registryCredential) {
dockerImage.push()
}
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
stage('Docker Run') {
steps {
echo 'Pull Docker Image & Docker Image Run'
sshagent (credentials: ['SSH Credential ID -> ssh']) {
sh "ssh -o StrictHostKeyChecking=no [Spring Boot Server username]@[Spring Boot Server IP 주소] 'docker pull [도커이미지 이름]'"
sh "ssh -o StrictHostKeyChecking=no [Spring Boot Server username]@[Spring Boot Server IP 주소] 'docker ps -q --filter name=[컨테이너 이름] | grep -q . && docker rm -f \$(docker ps -aq --filter name=[컨테이너 이름])'"
sh "ssh -o StrictHostKeyChecking=no [Spring Boot Server username]@[Spring Boot Server IP 주소] 'docker run -d --name [컨테이너 이름] -p 8080:8080 [도커이미지 이름]'"
}
}
}
}
post {
success {
slackSend (channel: '#채널명', color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
failure {
slackSend (channel: '#채널명', color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
}
}
위와 같은 Github Webhook 설정으로는 모든 branch의 push event에 Pipeline이 동작하게 된다.
특정 branch push event에만 동작하도록 하기 위해서는 추가 플러그인과 설정이 필요한 것 같은데 더 공부가 필요하다.
여러 비밀정보가 들어있는 application.yml을 Github에 올라가지 않도록 해놓았는데 해당 application.yml이 resources 폴더에 있지않아 Gradle Build 오류가 계속 발생했다.
따로 application.yml을 서버에 넣어주고 이를 copy하여 루트 디렉토리에 위치시키게 하고 java -jar
실행시 -Dspring.config.location=/application.yml
과 같이 동작하도록 Dockerfile을 구성해보기도 했으나 test 시 오류가 발생했다.
따라서 현재는 그냥 workspace 내부 resources 폴더에 직접 넣어놓았다. 하지만 더 좋은 방법을 찾아봐야겠다.
이번이 Jenkins로 CI/CD를 구축하는 게 세 번째인데 이제야 좀 알겠다.. Jenkins랑 점점 정드는 중.. 더 공부해야지~