나는야 인간 젠킨스 … 수동 배포를 맡고 있지. 😎😂
두 프로젝트를 진행하면서 본사 외부에서 본사 개발 서버에 배포 요청이 들어올 때마다 곤란하곤 했다. 또 작업 후에 배포까지 바로 못해드리는 경우가 있어서 수정사항 확인이 늦어지곤 했는데 이를 해결 하기 위해 CI/CD를 구축해보고자 마음먹었다.
아래는 ssh 포트 개방 불가 문제를 해결하기 위해 생각하며 작성했던 메모들이다. 기록을 위함이 참고는 말자 ㅎ
1. 깃 액션에 vpn 설정해서 내 vpn 계정으로 사내망 접근
[Access Private Server with GitHub Actions (VPN connection)](https://medium.com/@mahmutali.mas/access-private-server-with-github-actions-vpn-connection-cf20313abdd5)
- vpn 접근울 위한 인증서 필요 → 인프라팀에 요청해보자.
- vpn 관련하여 관리하고 있는 인증서가 없다고함.
- vpn 설정해서 사내망 접근 방법은 불가…
2. github Action IP 주소를 확인해서 인바운드 규칙에 적용하고 배포 후에 인바운드 규칙에서 제거
- 고정 ip 가 아니고 매번 배포 할때마다 ip 주소 확인해야함 → 자동 배포의 의미가 없음
- 보안 취약 위험성 있음
3. 배포 서버에 셀프 호스티드 러너 서버를 도커 컨테이너로 띄운 다음에 거기에 java랑 빌드 도구 설치해두고 여기서 빌드 후 빌드가 다 되면 배포 컨테이너로 배포 파일 옮겨서 배포
- 성공!
4. 젠킨스……
1. Git에서 변경 사항을 푸시하면 GitHub Actions가 동작.
2. Self-hosted runner 컨테이너가 빌드 작업을 수행
3. 빌드된 .war 파일을 웹서버 컨테이너로 이동.
4. 웹서버 컨테이너를 재시작하여 새로운 버전의 애플리케이션을 배포.
아래 작업들을 Dockerfile에 작성하여 한번에 해결 할 수 있도록 정리하였다.
1. CentOS 8 컨테이너를 생성
2. 컨테이너 내에 Docker를 설치하여 컨테이너 내부에서 .war 파일을 옮길 수 있도록 구성
3. Java 및 Maven을 설치하고 빌드 환경 설정
# CentOS 8 이미지를 기반으로 시작
FROM centos:8
# BaseOS, Extras, AppStream 리포지토리 파일 수정 (vault.centos.org로 변경)
RUN sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/CentOS-Linux-BaseOS.repo \
&& sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Linux-BaseOS.repo \
&& sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/CentOS-Linux-Extras.repo \
&& sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Linux-Extras.repo \
&& sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/CentOS-Linux-AppStream.repo \
&& sed -i 's|^#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Linux-AppStream.repo \
&& sed -i 's|$releasever|8.5.2111|g' /etc/yum.repos.d/CentOS-Linux-BaseOS.repo /etc/yum.repos.d/CentOS-Linux-Extras.repo /etc/yum.repos.d/CentOS-Linux-AppStream.repo
# YUM 캐시를 정리하고 새로고침
RUN yum clean all && yum makecache
# 시스템의 패키지 목록을 업데이트하여 최신 버전으로 유지
RUN yum update -y
# Docker 설치를 위해 필요한 도구 패키지 설치 (yum-utils)
RUN yum install -y yum-utils
# Docker의 공식 저장소를 시스템에 추가하여 Docker 패키지 다운로드 가능하게 설정
RUN yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Docker CLI를 포함한 Docker 패키지 설치
RUN yum install -y docker-ce-cli
# Docker 버전 확인 (설치가 제대로 되었는지 검증)
RUN docker --version
# Java 11 OpenJDK 설치
RUN yum install -y java-11-openjdk
# Maven 설치
RUN yum install -y maven
# 기본 쉘로 시작
CMD ["/bin/bash"]
# --------------------------------------
# --------------------------------------
# 빌드
# docker build -t cicd-docker-image .
# 실행
# docker run -it --name my_cicd_container -v /var/run/docker.sock:/var/run/docker.sock cicd-docker-image /bin/bash
만약 위 도커파일이 제대로 빌드 되지 못한다면 아래 과정을 손수 경험하자 ^_^
잘 실행된다면 4번(GitHub 페이지에서 self-hosted runner 생성 및 컨테이너 내에 action-runner 설치 및 연결)으로 바로 넘어가자.
docker pull centos:8
[참고] centos 버전을 7이 아닌 8을 사용하는 이유
docker run -it --name cicd_runner -v /var/run/docker.sock:/var/run/docker.sock centos:8 /bin/bash
/var/run/docker.sock
을 마운트 하는 이유 : 호스트 머신의 Docker 엔진에 접근해 docker cp
명령어를 사용하기 위함.
docker.sock
파일을 마운트하여 컨테이너를 생성하고, Docker CLI를 설치하여 컨테이너 내에서 실행된 Docker 명령어가 호스트의 Docker 환경에 영향을 미치게 된다. 따라서, 이 방법을 사용할 때는 주의가 필요하다.이러한 설정을 통해, runner 서버에서 호스트의 Docker 환경을 활용하여 컨테이너를 관리하고 빌드 작업을 수행할 수 있다.
CentOS 8이 2021년 12월 31일에 공식적으로 지원이 종료되면서, 기본 리포지토리(mirrorlist.centos.org)는 더 이상 CentOS 8 패키지에 대한 업데이트나 접근을 제공하지 않는다. 이로 인해 CentOS 8에서 기본 mirrorlist
경로를 사용하면 패키지 다운로드에 실패하게 된다. 이를 접근 가능한 리포지토리 url로 변경해주어야 한다.
yum update
위 명령어를 입력 시 아래와 같이 오류가 발생한다면 아래 링크를 참고하여 해결하자.
[Centos]Failed to download metadata for repo 'appstream' 에러 해결
docker cp 명령어를 사용하기 위해 컨테이너 내에서 docker CLI를 설치한다.
앞서 말했다시피 /var/run/docker.sock 을 마운트 하여 컨테이너를 생성했기 때문에 호스트 머신의 Docker 엔진에 접근이 가능해졌다.
# 시스템의 패키지 목록을 업데이트하여 최신 버전으로 유지
yum update
# Docker 설치를 위해 필요한 도구 패키지 설치 (yum-utils)
yum install -y yum-utils
# Docker의 공식 저장소를 시스템에 추가하여 Docker 패키지 다운로드 가능하게 설정
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Docker CLI를 포함한 Docker 패키지 설치
yum install -y docker-ce-cli
# Docker 버전 확인 (설치가 제대로 되었는지 검증)
docker --version
+) 만약 설치가 제대로 된 것 같은데 실행이 안된다면 /var/run/docker.sock 의 소유계정과 그룹을 살펴보자.
[Docker] permission denied while trying to connect to the Docker daemon socket at unix 에러 해결
+) 자바를 설치하긴 했는데 워크플로우 타면서 java 인식이 안되서 빌드가 안되는 문제가 있었다.
그래서 나는 워크플로우에 자바 set 하는 단계를 추가하여 해결하였다. 만약 java 설치에 애를 먹는다면 넘어가도 될 것 같다.
# 설치
yum install -y java-11-openjdk
# 설치 확인
java --version
# 설치
yum install maven -y
# 설치 확인
mvn -v
Download
아래 과정은 GitHub Actions의 Self-hosted Runner를 설치하는 과정이다. 위에서 runner 로 사용할 컨테이너를 생성해두었으니 그 컨테이너 내부에서 아래 명령어를 하나씩 입력하자.
# 폴더 생성
$ mkdir actions-runner && cd actions-runner
# 최신 러너 패키지 다운로드
$ curl -o actions-runner-linux-x64-2.320.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.320.0/actions-runner-linux-x64-2.320.0.tar.gz
# 선택 사항: 해시 검증
# 해시 검증 패키시 다운
$ yum install -y perl-Digest-SHA
# 해시 검증
$ echo "93ac1b7ce743ee85b5d386f5c1787385ef07b3d7c728ff66ce0d3813d5f46900 actions-runner-linux-x64-2.320.0.tar.gz" | shasum -a 256 -c
# 설치 프로그램 추출
$ tar xzf ./actions-runner-linux-x64-2.320.0.tar.gz
Configure
# GitHub Actions Self-Hosted Runner를 GitHub 리포지토리에 등록
$ ./config.sh --url https://github.com/An0401na/CICD-Test --token AXUUDYARWPNHASTYBPWTWPLHFDNX6
# 마지막 단계, 실행하기!
$ ./run.sh
[트러블 슈팅] Must not run with sudo
⚽ 발생 오류
[root@9ca5fc8c7aa5 actions-runner]# ./config.sh --url https://github.com/An0401na/CICD-Test --token
Must not run with sudo
⚽ 발생원인
./config.sh 스크립트를 실행할 때 "Must not run with sudo"라는 오류 메시지가 나타나는 것은 스크립트가 sudo로 실행되었기 때문이다. GitHub Actions의 self-hosted runner 설정 시 sudo를 사용하지 않아야 한다.
아래 ./config.sh 명령어를 실행하기 전에 runner를 관리할 사용자 계정을 생성해야 한다.
⚽ 해결방법
컨테이너 내부에서 docker.sock을 사용하려면, docker 그룹과 컨테이너 내부의 사용자 설정이 일치해야 한다. 이를 위해 컨테이너 내부에서 docker 그룹 ID를 호스트와 동일하게 맞추고, 해당 사용자 계정을 docker 그룹에 추가하는 방법이 있다.
# 컨테이너 내부에서 실행
# docker.sock 파일의 소유자를 확인하려면 다음 명령어를 사용
$ ls -l /var/run/docker.sock
# -- 아래와 같은 형식으로 출력
srw-rw----. 1 root 1001 0 Oct 2 02:22 /var/run/docker.sock
- root는 파일의 소유자(user)
- 1001는 파일의 소유 그룹(group)ID
docker.sock
을 소유하고 있는 그룹은 docker
이고 docker
의 그룹 ID가 1001임을 기억하자.
runner 컨테이너 내부에서 아래 명령어 실행
# 컨테이너 내부에서 실행
# 컨테이너 내부에서 docker 그룹의 GID가 호스트와 다르므로, 호스트와 동일한 GID를 갖도록 컨테이너 내부의 docker 그룹을 수정
groupmod -g 1001 docker
# runner라는 새 사용자를 생성
$ useradd runner
# docker 그룹에 추가
$ usermod -aG docker runner
# 사용자 계정 변경
su runner
# 도커 사용 가능 확인
$ docker ps
[트러블 슈팅] Must not run with sudo
⚽발생 오류
[runner@1fe76c6022dc actions-runner]$ ./config.sh --url https://github.com/An0401na/CICD-Test --token AXUUDYFGWY3AOXFA6SULMMDHFDVIM
ldd: warning: you do not have execution permission for `./bin/libcoreclr.so'
ldd: warning: you do not have execution permission for `./bin/libSystem.Security.Cryptography.Native.OpenSsl.so'
ldd: warning: you do not have execution permission for `./bin/libSystem.IO.Compression.Native.so'
Libicu's dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.
⚽발생 원인
경고 메시지 ldd: warning: you do not have execution permission for
는 ./bin/libcoreclr.so
, ./bin/libSystem.Security.Cryptography.Native.OpenSsl.so
, ./bin/libSystem.IO.Compression.Native.so
파일들이 실행 권한이 없어 의존성 확인에 실패했음을 의미한다.
⚽해결 방법
실행 권한을 부여하여 의존성 설치 스크립트를 실행하여 의존성을 부여하자.
아래 명령어를 따라가며 해결하자.
# root로 계정 전환
$ exit
# 모든 바이너리 파일에 대해 실행 권한 부여
$ chmod +x ./bin/*
# 의존성 설치 스크립트 실행
$ ./bin/installdependencies.sh
# /actions-runner 디렉토리에 대한 쓰기 권한 부여
$ chmod -R 777 /actions-runner
# runner로 계정 전환
su runner
# GitHub Actions Self-Hosted Runner를 GitHub 리포지토리에 등록
$ ./config.sh --url https://github.com/An0401na/CICD-Test --token AXUUDYARWPNHASTYBPWTWPLHFDNX6
Runner를 생성하기 위한 몇가지 정보를 받는다. 나는 아래와 같이 입력했지만 선택사항이므로 잘 해석해서 입력하면 된다.
# Runner Registration
Enter the name of the runner group to add this runner to: [press Enter for Default]
Enter the name of runner: [press Enter for 1fe76c6022dc] CICD-Test
This runner will have the following labels: 'self-hosted', 'Linux', 'X64'
Enter any additional labels (ex. label-1,label-2): [press Enter to skip] self-hosted,centos8,docker
√ Runner successfully added
√ Runner connection is good
# Runner settings
Enter name of work folder: [press Enter for _work] cicd_work
√ Settings Saved.
이로써 GitHub Action Self-hosted runner가 정상적으로 생성되었다.
$ ./run.sh
실행하면
이렇게 리스닝 상태임을 확인할 수 있다.
Github 페이지의 Action 탭을 활용해 자동으로 생성할 수도 있고,
레포명/.github/workflows/????.yml 로 생성하여 워크플로우를 작성할 수도 있다.
나는 후자를 택하였다.
CICD-Test/.github/workflows/cicd.yml 로 지정하였다.
name: CI/CD Pipeline # 파이프라인의 이름 정의
on:
push: # 'push' 이벤트에 대한 트리거 설정
branches:
- main # 'main' 브랜치에 푸시될 때 트리거 발생
pull_request: # 'pull_request' 이벤트에 대한 트리거 설정
branches:
- main # 'main' 브랜치로의 PR 생성 시 트리거 발생
jobs:
build:
runs-on: self-hosted # 셀프 호스티드 러너에서 이 작업이 실행됨 (GitHub 호스티드 대신 로컬 서버에서 실행)
steps:
- name: Checkout code # 코드를 체크 아웃하는 단계
uses: actions/checkout@v2 # GitHub Actions에서 제공하는 체크아웃 액션 사용
# 이 단계에서는 레포지토리의 코드를 셀프 호스티드 러너로 가져옵니다.
- name: Set up JDK 11 # JDK 11을 설정하는 단계
uses: actions/setup-java@v1 # GitHub Actions에서 제공하는 Java 설정 액션 사용
with:
java-version: '11' # 사용할 Java 버전을 '11'로 지정
# 이 단계에서는 JDK 11을 설치하여 빌드 환경을 준비합니다.
- name: Build with Maven # Maven을 사용하여 프로젝트 빌드
run: mvn package # Maven으로 패키지 명령 실행
# 이 단계에서는 Maven을 사용해 프로젝트를 빌드하여 .war 파일을 생성합니다.
# Gradle을 사용하는 경우, ./gradlew build 명령을 사용해야 합니다.
- name: Backup existing ROOT.war and copy new WAR file # 기존 WAR 파일 백업 및 새 WAR 파일 복사
run: |
TIMESTAMP=$(date +%Y%m%d%H%M%S) # 현재 시간 포맷: YYYYMMDDHHMMSS
# 현재 시간을 기준으로 타임스탬프를 생성하여 백업 파일 이름에 사용
# 기존 ROOT.war 파일을 ROOT.war.<현재시간> 으로 백업
docker exec web_container mv /usr/local/tomcat/webapps/ROOT.war /usr/local/tomcat/webapps/ROOT.war.$TIMESTAMP
# 기존 웹 컨테이너 내의 ROOT.war 파일을 백업
# 새로운 WAR 파일을 컨테이너에 복사
docker cp target/test-0.0.1-SNAPSHOT.war web_container:/usr/local/tomcat/webapps/ROOT.war
# Maven 빌드로 생성된 새로운 WAR 파일을 웹 컨테이너에 복사
# 이 단계에서는 기존 Tomcat 서버의 ROOT.war 파일을 백업하고, 새로운 WAR 파일을 덮어씌웁니다.
- name: Restart web server container # 웹 서버 컨테이너 재시작
run: |
docker restart web_container
# Tomcat 웹 서버가 실행 중인 Docker 컨테이너를 재시작하여 새로운 WAR 파일이 반영되도록 합니다.
Checkout code
: 레포지토리의 코드를 셀프 호스티드 러너로 가져오는 단계입니다. 이 작업이 완료되면 로컬에서 코드를 사용할 수 있습니다.Set up JDK 11
: 이 단계는 Java 11 버전을 빌드 환경에 설치하고 설정합니다. 이후 Maven을 통해 프로젝트를 빌드할 때 Java 11을 사용할 수 있게 됩니다.Build with Maven
: Maven을 사용하여 Java 프로젝트를 빌드하고, 패키징된 결과물인 .war
파일을 생성합니다.Backup existing ROOT.war and copy new WAR file
: 이 단계에서는 Tomcat 컨테이너 내의 기존 ROOT.war
파일을 백업하고, 새로 빌드된 .war
파일을 Tomcat 컨테이너로 복사합니다.Restart web server container
: Tomcat 웹 서버가 실행되는 Docker 컨테이너를 재시작하여 새 .war
파일이 반영되도록 합니다.이런 구조로 CI/CD 파이프라인을 구성하면 코드가 푸시되거나 PR이 생성될 때 자동으로 빌드 및 배포가 이루어집니다.
위와 같이 작성 후 main 브랜치에 커밋하게 되면 아래와 같이 Action 탭에서 요청한 워크플로우가 돌아감을 확인할 수 있다.
CI/CD 워크플로우가 잘 돌아가서 배포가 성공적으로 되었다 !
슬랙 알람을 추가 했고 active profile 이 올바르게 지정되었는지 확인하는 작업을 추가하였다.
그리고 빌드와 배포 과정을 나누었고 빌드가 제대로 됐을때만 배포가 이뤄지게 하였다.
name: CI/CD Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Notify Slack - Build Started
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"[CI/CD] Build has started by ${GITHUB_ACTOR}.\"}" ${{ secrets.WEBHOOK_SLACK_URL }}
- name: Check active profile
run: |
if ! grep -q 'spring.profiles.active=main' src/main/resources/application.properties; then
echo "Error: spring.profiles.active is not set to 'main'."
exit 1 # 프로파일이 맞지 않으면 빌드 실패
fi
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Notify Slack - Build Status
run: |
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"[CI/CD] Build Status: Success by ${GITHUB_ACTOR}.\"}" ${{ secrets.WEBHOOK_SLACK_URL }}
deploy:
runs-on: self-hosted
needs: build # 'build' 작업이 성공해야 이 작업이 실행됨
if: github.event_name == 'push' && github.ref == 'refs/heads/main' # main 브랜치에 푸시될 때 실행됨
steps:
- name: Backup existing ROOT.war and copy new WAR file
run: |
TIMESTAMP=$(date +%Y%m%d%H%M%S) # 현재 시간 포맷: YYYYMMDDHHMMSS
# 기존 ROOT.war 파일을 ROOT.war.<현재시간> 으로 백업
docker exec web_container mv /usr/local/tomcat/webapps/ROOT.war /usr/local/tomcat/webapps/ROOT.war.$TIMESTAMP
# 새로운 WAR 파일을 컨테이너에 복사
docker cp target/test-0.0.1-SNAPSHOT.war web_container:/usr/local/tomcat/webapps/ROOT.war
- name: Restart web server container
run: |
docker restart web_container
다음에는 도커 레포지토리를 통한 빌드와, 젠킨스를 활용한 CI/CD 구축에 도전해봐야겠다.