도커와 젠킨스를 이용한 CI/CD 파이프라인 구성
CI (Continuous Integration)
- 개발자가 작성한 코드가 특정 시간이 아닌 지속적으로 배포되어야 할 통합본에 통합되는 것
- 이를 통해 언제든지 필요에 따라 수정된 코드가 반영되고 통합되어 자동으로 배포될 수 있음
CD (Continuous Deployment)
- CI 단계에서 통합된 소스를 repo로 자동 릴리즈 하는 단계를 의미함
- 준비된 빌드를 사람의 개입 없이 자동으로 릴리즈 함
실습: 서버 생성하고 도커/젠킨스 설정
-
AWS Lightsail (EC2보다 저렴하고 설정이 간단) 생성
- 플랫폼은 Linux, 블루프린트는 OS Only, CentOS 7
- SSH 키 페어는 기본키를 선택
- 메모리 1GB 선택
-
둘다 인스턴스 터미널에 접속해 도커 설치
$ curl -fsSl https://get.docker.com -o get-docker.sh # 슈퍼유저 권한으로 실행 $ sudo sh get-docker.sh # 현재 계정을 docker 그룹에 추가 # centos 접속 상태로 docker 명령어를 sudo 없이 수행 가능해짐 # 참고로 현재 터미널 세션에는 적용되지 않으므로 껐켰 ㄱㄱ $ sudo usermod -aG docker centos # 도커 실행 및 인스턴스 재부팅되어도 자동 실행되도록 함 $ sudo systemctl start docker $ sudo systemctl enable docker $ docker ps
-
젠킨스도 설치
# 권한 문제가 뜨면 소유권을 centos로 변경해야 함 $ docker run -v /var/run/docker.sock:/var/run/docker.sock \ -v /srv/jenkins/home:/var/jenkins_home \ -p 8080:8080 --name jenkins jenkins/jenkins $ ls -l /srv/jenkins total 0 drwxr-xr-x. 2 root root 6 Nov 15 11:06 home $ sudo chown -R centos:centos /srv/jenkins/home/ total 0 drwxr-xr-x. 2 centos centos 6 Nov 15 11:06 home # 종료상태인 젠킨스 컨테이너 제거 후 다시 실행 # 젠킨스 최초 접속 시 필요한 패스워드니까 기록해두기 $ docker rm jenkins $ docker run -v /var/run/docker.sock:/var/run/docker.sock \ -v /srv/jenkins/home:/var/jenkins_home \ -p 8080:8080 --name jenkins jenkins/jenkins Jenkins initial setup is required. An admin user has been created and a password generated. Please use the following password to proceed to installation: {password} This may also be found at: /var/jenkins_home/secrets/initialAdminPassword # 백그라운드로 실행하는 d 옵션을 추가해 젠킨스 컨테이너 실행 $ docker rm jenkins $ docker run -v /var/run/docker.sock:/var/run/docker.sock \ -v /srv/jenkins/home:/var/jenkins_home \ -p 8080:8080 -d --name jenkins jenkins/jenkins
-
콘솔에서 centOS > manage > networing 이동
- IPv4 Firewall에 8080 포트를 추가
- public IP 주소를 복사해 :8080 포트 붙여서 웹브라우저로 접속
-
컨테이너 내부에서 도커 명령어 사용할 수 있게 설정
$ cat /etc/group | grep docker docker:x:994:centos # 도커 컨테이너 루트로 접속함 # -u 0 옵션은 사용자를 루트로 접속시키라는 의미 $ docker exec -it -u 0 jenkins bash root@~~~: # 이때 도커 바이너리는 본인에게 설치된 버전 링크로 복붙 root@~~~: curl -fsSl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.21.tgz -o docker-20.10.21.tgz root@~~~: ls -l docker-20.10.21.tgz -rw-r--r--. 1 root root 65976998 Nov 15 11:36 docker-20.10.21.tgz # 다운로드한 바이너리 파일 압축해제 root@~~~: tar xvfz docker-20.10.21.tgz root@~~~: cp ./docker/docker /usr/bin root@~~~: ls -l /usr/bin/docker -rwxr-xr-x. 1 root root 48047088 Nov 15 11:38 /usr/bin/docker # 젠킨스 컨테이너 내부에 docker 그룹 추가 # centOS-1의 docker 그룹과 동일한 ID(994) 추가 root@~~~: groupadd -g 994 docker root@~~~: cat /etc/group | grep docker # 젠킨스 컨테이너 내부에 jenkins 계정 추가 root@~~~: usermod -aG docker jenkins root@~~~: id jenkins uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins),994(docker) root@~~~: exit # 젠킨스 계정으로 젠킨스 컨테이너에 접속 # 본인 도커허브 로그인할 때 이메일 말고 ID 써야함 $ docker exec -it jenkins bash $ docker ps $ docker login ... Login Succeeded $ exit $ docker restart jenkins
실습: github와 젠킨스 연동 설정
- 어쩌구저쩌구 깃헙 repo 하나 생성
- Settings > Webhooks > add webhook
http://{centOS-1 IP주소}:8080/github-webhook
- 이제 해당 repo에 푸시 이벤트가 발생하면 젠킨스로 웹훅이 전송된다.
- develop 브랜치로 해당 동작을 수행하기 위해 젠킨스에 추가한다.
- 새로운 Item > Freestyle project
- 소스코드 관리 >
- git 체크
- repo URL 입력 (https://github.com/wacilpong/jenkins)
- branches to build 입력 (
*/develop
)
- 빌드유발 > github hook trigger 체크
- Build > add build step > execute shell 추가
docker build -t nginx:test . && sh container_check.sh
- 이때
-t
는 태그의 의미로, test라는 태그가 있는 nginx를 빌드하라는 뜻이다. - 로컬에 있는 Dockerfile 이용해 nginx 이미지 생성, 이미지에 test라는 태그를 부여한다.
- 이후에 container_check.sh 스크립트를 수행한다.
- 빌드 후 조치 > Delete workspace when build is done
실습: nginx 예제 프로젝트 가져와 develop 브랜치에 푸시하기
$ wget https://github.com/startbootstrap/startbootstrap-freelancer/archive/gh-pages.zip
$ unzip gh-pages.zip
$ cp -r startbootstrap-freelancer-gh-pages/* ./
$ rm -rf gh-pages.zip startbootstrap-freelancer-gh-pages/
$ vi Dockerfile
FROM nginx:latest
COPY . /usr/share/nginx/html
$ vi container_check.sh
NGINX_CONTAINER_ID=`docker ps -aq --filter 'name=nginx'`
if [ -n "$NGINX_CONTAINER_ID" ];
then
echo "nginx container exist"
docker stop $NGINX_CONTAINER_ID
docker rm $NGINX_CONTAINER_ID
docker run -d -p 80:80 --name nginx nginx:test
else
echo "nginx container not exist"
docker run -d -p 80:80 --name nginx nginx:test
fi
$ ga .
$ g commit -m "ADD test files"
$ gp -u origin develop
- 이후 본인 centOS-1 public IP 접속하면 화면이 보일 것이다. (80이므로 포트 생략가능)
- jenkins에 main 브랜치용 item을 하나 더 생성
- execute shell 체크하고 아래 명령 작성
docker build -t roomyhan/nginx:prod . && docker push roomyhan/nginx:prod
- 이외에 모든 설정은 develop과 같음
실습: 빌드된 도커 이미지가 도커허브에 푸시되면 자동 실행
-
jenkins 관리 > 플러그인 관리 > Available plugins
publish over SSH
,CloudBase docker hub/registry noti
설치
centOS-1에서 centOS-2로 접속해 배포를 하려면 권한이 있어야 하니까 필요!- 이후 jenkins 껐켰 or 도커 컨테이너 지우고 다시 띄우기
- 시스템 설정 가보면 Publish over SSH 탭이 생긴다.
- Key에 centOS 인스턴스 생성 시 설정한 default key를 복사한다.
cat ~/Downloads/LightsailDefaultKey-ap-northeast-2.pem
--BEGIN~
부터 끝까지 다 복사해야 한다.
- SSH servers 추가
- hostname은 centOS-2 public IP를 입력한다.
- username은
centos
이다. - 이후 Test Configuration을 클릭해 테스트가 성공하면 최종 저장한다.
-
본인 도커허브의 nginx 레포 > Webhook > Add Webhook
- name:
nginx-production-server-common
- url:
http://{lightsail public IP}:8080/dockerhub-webhook/notify
- name:
-
jenkins에 도커허브용 item 생성
- 빌드 유발 >
monitor docker hub...
하위 모두 체크 - repo에는
{본인도커허브ID}/nginx
입력 - Build Steps > send files… > exec command에 아래 명령을 입력한다.
docker pull roomyhan/nginx:prod && sh container_check.sh
- 도커허브에 새로운 이미지가 업로드되면 nginx:prod를 로컬로 다운로드하고 sh 실행시킨다.
- 해당 sh은 새로 갱신된 이미지를 기반으로 컨테이너를 실행시킨다.
- 즉, 가장 최근에 도커허브로 전달된 이미지로 컨테이너가 배포된다.
- 빌드 유발 >
-
centOS-2 루트에 container_check 스크립트를 생성한다.
#/bin/sh NGINX_CONTAINER_ID=`docker ps -aq --filter 'name=nginx'` if [ -n "$NGINX_CONTAINER_ID" ]; then echo "nginx container exist" docker stop $NGINX_CONTAINER_ID docker rm $NGINX_CONTAINER_ID docker run -d -p 80:80 --name nginx roomyhan/nginx:prod else echo "nginx container not exist" docker run -d -p 80:80 --name nginx roomyhan/nginx:prod fi
지금까지의 설정을 통해 수행되는 일들
- develop에 푸시하면 github 웹훅에 의해 centOS-1에서 동작하는 젠킨스가 develop의 코드를 받아온다.
- 받아온 코드를 기반으로 nginx 이미지를 생성하고 nginx 컨테이너를 실행시킨다.
- centOS-1의 public IP로 접속하면 수정사항이 반영되어 있다.
- develop을 main으로 머지하면 nginx 이미지를 생성해 도커허브에 업로드한다.
- 도커허브에서 젠킨스에 웹훅을 보내 새로운 이미지를 centOS-2에서 컨테이너로 실행한다.
- 즉, 젠킨스는 SSH를 통해 centOS-2 도커허브에서 이미지를 받아온다.
- 그리고 container_check 스크립트를 실행해 갱신된 이미지 기반의 컨테이너를 실행한다.
- centOS-2의 public IP로 접속하면 수정사항이 반영되어 있음을 알 수 있다.
시행착오
-
/tmp/jenkins12655071034461658190.sh: 2: docker: not found
빌드에러- centOS-1에서 젠킨스 계정으로 도커 루트로 들어간다.
(docker exec -it -u 0 jenkins bash) - 젠킨스 컨테이너 내부에 docker 그룹을 다시 추가했다.
- centOS-1에서 젠킨스 계정으로 도커 루트로 들어간다.
-
if [ -n "$NGINX_CONTAINER_ID"];
- 위처럼 맨 끝 괄호 띄어쓰기를 붙여서 작성하면 안된다.
- 이거 못찾아서 30분이나 헤맸다.
- sh 작성할 때는 띄어쓰기 같은 사소한 것들을 주의하자.
-
도커로 띄운 젠킨스 id, password 까먹음
-
그래서 띄운 lightsail centos 인스턴스 터미널 진입해서 아래와 같이 실행
# 도커로 띄운 jenkins 컨테이너 터미널 진입 $ docker exec -it -u 0 jenkins bash # 젠킨스 홈 디렉터리 확인 root@~~~: ${JENKINS_HOME} bash: /var/jenkins_home: Is a directory # config.xml 편집 (vim이 없다면 설치하자) # apt-get update | apt-get install vim root@~~~: vim /var/jenkins_home/config.xml # 에디터에서 <useSecurity>를 false # authorizationStrategy 부분을 주석(<!--...-->) # 이후 컨테이너 터미널에서 나와서 컨테이너를 다시 띄우기 $ docker stop jenkins $ docker rm jenkins $ docker run -v /var/run/docker.sock:/var/run/docker.sock \ -v /srv/jenkins/home:/var/jenkins_home \ -p 8080:8080 -d --name jenkins jenkins/jenkins # 그리고 얼른 보안 관리 가서 아래처럼 다시 설정해주자 # Security Realm: jenkins' own user database # Authorization: Logged-in users can do anything
-