오늘은 CI/CD를 구현하면서 정리한 모든 것을 해볼 것이다. 스프링 프로젝트는 이미 구현해놓았다.
해당 글은 "무중단 배포"에 대한 내용은 없습니다.
해당 프로젝트 Git 주소: https://github.com/Cha-Young-Ho/docker_mysql_ec2
Git Flow를 통해서 모든 작업을 총괄해야 한다. 사실상 가장 중요한 부분이 될 것이다.
작성한 Git Flow를 살펴보자.
on:
push: # feature/*와 develop 브랜치에서 push가 일어났을 때 github action 동작
branches:
- 'master'
- 'develop'
pull_request: # feature/*와 develop 브랜치에서 PR이 일어났을 때 github action 동작
branches:
- 'master'
- 'develop'
# 참고사항
# push가 일어난 브랜치에 PR이 존재하면, push에 대한 이벤트와 PR에 대한 이벤트 모두 발생합니다.
jobs:
build:
runs-on: ubuntu-latest # 실행 환경 지정
steps:
- uses: actions/checkout@v2 # github action 버전 지정(major version)
- name: Set up JDK 11 # JAVA 버전 지정
uses: actions/setup-java@v1
with:
java-version: 11
- name: Grant execute permission for gradlew
run: chmod +x gradlew
## create application-database.yaml
- name: make application-database.yaml
run: |
# create application-database.yaml
cd ./src/main/resources
# application-database.yaml 파일 생성
touch ./application-database.yaml
# GitHub-Actions 에서 설정한 값을 application-database.yaml 파일에 쓰기
echo "${{ secrets.DATABASE }}" >> ./application-database.yaml
shell: bash
- name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외)
run: ./gradlew build -x test
- name: Test with Gradle # test application build
run: ./gradlew test
- name: Publish Unit Test Results # test 후 result를 보기 위해 추가
uses: EnricoMi/publish-unit-test-result-action@v1
if: ${{ always() }} # test가 실패해도 report를 남기기 위해 설정
with:
files: build/test-results/**/*.xml
# 배포 Job
# 도커허브 push
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t app .
docker tag app ${{ secrets.DOCKER_REPO }}/cicd
docker push ${{ secrets.DOCKER_REPO }}/cicd
# ec2 접속 및 도커 pull
# 도커 실행
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.AWS_HOST }} # EC2 인스턴스 퍼블릭 DNS
username: ubuntu
key: ${{ secrets.AWS_SSH_KEY }} # pem 키--
# 도커 작업
script: |
docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
docker stop server
docker network create -d bridge test
docker run -d --network test --network-alias mysql -v /build/DB/mysql:/var/lib/mysql --name mysqlDB -e MYSQL_DATABASE=anonymous_board -e MYSQL_USER=user01 -e MYSQL_PASSWORD=user01 -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql
docker run -d --rm --name app --network test -p 8080:8080 ${{ secrets.DOCKER_ID }}/cicd:latest
이제 구문 by 구문 살펴보자!
on:
push: # feature/*와 develop 브랜치에서 push가 일어났을 때 github action 동작
branches:
- 'master'
- 'develop'
pull_request: # feature/*와 develop 브랜치에서 PR이 일어났을 때 github action 동작
branches:
- 'master'
- 'develop'
master, develop 브랜치에 "push", "pr" 이벤트가 생겼을 때 git action을 동작시킨다.
jobs:
build:
runs-on: ubuntu-latest # 실행 환경 지정
job을 명시하며 ubuntu 환경에서 수행된다고 명시
Job의 구체적인 동작을 명시한다. 순차적으로 재생이 된다.
steps:
- uses: actions/checkout@v2
git action의 버전을 명시해준다.
구체적인 행동 하나하나를 명시해준다.
- name: Set up JDK 11 # JAVA 버전 지정
uses: actions/setup-java@v1
with:
java-version: 11
git action runner에 jdk 버전을 세팅해준다.
- name: Grant execute permission for gradlew
run: chmod +x gradlew
gradlew에 실행 권한을 준다.
- name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외)
run: ./gradlew build -x test
- name: Test with Gradle # test application build
run: ./gradlew test
- name: Publish Unit Test Results # test 후 result를 보기 위해 추가
uses: EnricoMi/publish-unit-test-result-action@v1
if: ${{ always() }} # test가 실패해도 report를 남기기 위해 설정
with:
files: build/test-results/**/*.xml
여기까지가 완료되면 CI가 완료되었다.
CD를 구축하는 방식은 수없이 많다.
나는 2번의 방법으로 할 것이다.
그리고 Docker image를 컨테이너화 시키는데도 방법이 2가지가 존재한다.
나는 둘 다 해볼 것이다.
먼저 runner에서 수행된 내용을 이미지로 만들어야 한다.
# 배포 Job
# 도커허브 push
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t app .
docker tag app ${{ secrets.DOCKER_REPO }}/cicd
docker push ${{ secrets.DOCKER_REPO }}/cicd
환경변수 살펴보기.
순서를 살펴보면
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.AWS_HOST }} # EC2 인스턴스 퍼블릭 DNS
username: ubuntu
key: ${{ secrets.AWS_SSH_KEY }} # pem 키--
# 도커 작업
script: |
docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
docker stop server
docker network create -d bridge test
docker run -d --network test --network-alias mysql -v /build/DB/mysql:/var/lib/mysql --name mysqlDB -e MYSQL_DATABASE=anonymous_board -e MYSQL_USER=user01 -e MYSQL_PASSWORD=user01 -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql
docker run -d --rm --name app --network test -p 8080:8080 ${{ secrets.DOCKER_ID }}/cicd:latest
먼저 ssh로 나의 AWS EC2로 접속을 한다.
아래 사진을 보면 프라이빗 IP 아래에 있다.
cat <키 페어 파일>
다음과 같이 나온다.
을 하면 위와 같이 나온다.
위에서 ---를 다 포함하여 마지막 ---까지 복사해서 등록해주어야 한다.
❗️ %는 빼야함!!!!!!!!
이렇게하면 러너가 EC2에 SSH로 접속을 한다. 그리고 script를 실행한다.
다음으로는 도커 작업을 보자.
script: |
docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
docker stop server
docker network create -d bridge test
docker run -d --network test --network-alias mysql -v /build/DB/mysql:/var/lib/mysql --name mysqlDB -e MYSQL_DATABASE=anonymous_board -e MYSQL_USER=user01 -e MYSQL_PASSWORD=user01 -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql
docker run -d --rm --name app --network test -p 8080:8080 ${{ secrets.DOCKER_ID }}/cicd:latest
docker pull : 도커 허브에서 내가 runner로 올린 docker image를 받아온다.
docker tag ~ : 받아온 이미지에 태그를 명시 한다.
docker stop server : 기존에 존재하던 도커를 중지해준다.
docker network create : spring과 mysql이 같은 space에 동작하도록 network를 생성해준다.
docker run mysql : 도커에서 제공해주는 mysql을 사용한다.
docker run app : 내가 받아온 image를 실행해준다.
인스턴스 공개IP의 8080포트로 접속해보자.
위의 사진대로 나오면 성공!
Docker-Compose를 EC2로 전달하고 해당 Docker-Compose를 실행해준다.
Flow는
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.AWS_HOST }} # EC2 인스턴스 퍼블릭 DNS
username: ubuntu
key: ${{ secrets.AWS_SSH_KEY }} # pem 키--
# 도커 작업
script: |
docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
docker stop server
docker compose-up -d
이렇게 수정해준다.
그리고 docker-compose는 다음과 같이 구성되어있다.
version: "3"
services:
mysqlDB:
image: mysql
container_name: mysqlDB
environment:
- MYSQL_DATABASE=anonymous_board
- MYSQL_ROOT_PASSWORD=password
- MYSQL_USER=user01
- MYSQL_PASSWORD=user01
ports:
- 3306:3306
networks:
- test
app:
build: .
ports:
- 8080:8080
restart: on-failure
depends_on:
- mysqlDB
networks:
- test
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysqlDB:3306/anonymous_board?serverTimezone=UTC&characterEncoding=UTF-8
SPRING_DATASOURCE_USERNAME: user01
SPRING_DATASOURCE_PASSWORD: user01
networks:
test:
완료
아마 구현하면서 flow를 돌리면 다음의 에러를 만날 것이다.
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain
이 에러는 우분투 최신 버전에서 ssh에 대한 기본 정책때문에 생긴 에러이다.
다음의 경로로 이동해서 설정값을 하나 추가해주자!
경로 : /etc/ssh/sshd_config
해당 파일을
sudo vi sshd_config
하여, 다음의 라인을 추가해주자.
PubkeyAcceptedKeyTypes=+ssh-rsa
해당 문제 깃 이슈 페이지
도커 권한 때문에 에러가 난다.
다음의 명령어를 수행하자
sudo groupadd docker
sudo usermod -aG docker ${USER}
이렇게하고 인스턴스를 재부팅 해야 적용이 된다.
끝!