우당탕탕 CI/CD적용기
May 05, 2024

도커 이미지 빌드하고… 도커 이미지 푸시하고…
서버로 접속하여 이미지 풀하고 컨테이너 띄우고 하다보니 이제는 미루기보다 cicd를 구성하는게 덜 귀찮을 것 같다…
name: Java CI with Gradle to EC2
on:
push:
branches: [ "master" ]
env:
S3_BUCKET_NAME: gwangbu
RESOURCE_PATH: ./src/main/resources/application.yml
AWS_REGION: ap-northeast-2
CODE_DEPLOY_APPLICATION_NAME: gwangbu-code-deploy
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: gwangbu-code-deploy
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set yaml file
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.RESOURCE_PATH }}
env:
spring.security.oauth2.client.registration.naver.client-secret: ${{ secrets.NAVER_CLIENT_SECRET }}
spring.security.oauth2.client.registration.google.client-secret: ${{ secrets.GOOGLE_CLIENT_SECRET }}
spring.security.oauth2.client.registration.kakao.client-secret: ${{ secrets.KAKAO_CLIENT_SECRET }}
spring.security.oauth2.client.registration.github.client-secret: ${{ secrets.CLIENT_SECRET_GITHUB }}
jwt.secret: ${{ secrets.JWT_SECRET }}
spring.datasource.url: ${{ secrets.MYSQL_DATABASE }}
spring.datasource.username: ${{ secrets.MYSQL_USER }}
spring.datasource.password: ${{ secrets.MYSQL_PASSWORD }}
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
shell: bash
- name: Build with Gradle Wrapper
run: ./gradlew clean build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY_ID }}
aws-region: ${{ env.AWS_REGION }}
- name: Docker build & push to prod
if: contains(github.ref, 'master')
run: |
docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_SECRET}}
docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/oauth2 .
docker push ${{ secrets.DOCKER_USERNAME }}/oauth2
- name: Deploy to prod
uses: appleboy/ssh-action@master
id: deploy-prod
if: contains(github.ref,'master')
with:
host: ${{ secrets.HOST_DEV }}
username: ${{ secrets.HOST_USERNAME }}
password: ${{ secrets.HOST_PASSWORD }}
port: 22
script: |
sudo docker stop $(sudo docker ps -q) 2>/dev/null || true
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/oauth2
sudo docker run -d -p 8080:8080 --network login ${{ secrets.DOCKER_USERNAME }}/oauth2
sudo docker image prune -f
처음으로 git action yaml파일을 작성할 때는 빌드된 파일을 s3에 업로드하고, 다운받아 원격지에서 실행하는 방식을 사용했었다.
이후로 도커를 사용하다보니 action yaml파일을 이용해 docker를 실행할 수 있도록 구성해보았다.
aws ec2에서 default로 구성된 ubuntu 유저를 사용하다보니 password가 설정이 안되어있어 ec2로 접속할 때 pem키를 action의 secret에 저장하기 껄끄러워 비밀번호를 초기화해주었다.
ubuntu 사용자로 접속한 상태에서 passwd를 하니 초기 비밀번호를 몰라 su권한으로 접근하여 재설정해주었다.
passwd ${userid}
ex) passwd ubuntu
이렇게 아이디 패스워드를 이용해 접근이 가능하도록 설정하였다.
트러블 슈팅 1

git action jobs은 성공하였지만, 서버로 접속하여 도커 컨테이너를 조회해보니 변경사항이 적용되지 않았다.
sudo docker stop oauth2
sudo docker rm oauth2
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/oauth2
sudo docker run -d -p 8080:8080 --network login --name oauth2 ${{ secrets.DOCKER_USERNAME }}/oauth2
sudo docker image prune -f
ec2의 스크립트를 위와 같이 변경하였다.
컨테이너를 중지하고, 삭제하지 않아서 발생하는 문제였다.
docker rm -f {container name}
을 사용하면 가동중인 컨테이너를 강제 종료후 삭제까지 한번에 할 수 있지만 어떤 문제가 생길지 모르니
docker stop {container name}
docker rm {container name}
을 차례로 실행하도록 변경하였다.
+) 이전의 코드에서는 컨테이너의 이름을 지정해주지 않아서 실행중인 컨테이너를 찾고, 중시시켰는데, 컨테이너의 이름을 지정해주니 훨씬 스크립트의 가독성이 좋아졌다.
트러블 슈팅2

1번의 변경사항을 적용하니 서버에 새로운 컨테이너가 실행은 되었다.
하지만 기존에서 환경변수를 Actions secrets and variables에서 아래 처럼 가져왔는데, 내 프로젝트 패키지의 .yml파일은 용도 별로 분리해둬서 문제가 발생한 것이었다.
- name: Set yaml file
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.RESOURCE_PATH }}
env:
spring.security.oauth2.client.registration.naver.client-secret: ${{ secrets.NAVER_CLIENT_SECRET }}
spring.security.oauth2.client.registration.google.client-secret: ${{ secrets.GOOGLE_CLIENT_SECRET }}
spring.security.oauth2.client.registration.kakao.client-secret: ${{ secrets.KAKAO_CLIENT_SECRET }}
spring.security.oauth2.client.registration.github.client-secret: ${{ secrets.CLIENT_SECRET_GITHUB }}
jwt.secret: ${{ secrets.JWT_SECRET }}
spring.datasource.url: ${{ secrets.MYSQL_DATABASE }}
spring.datasource.username: ${{ secrets.MYSQL_USER }}
spring.datasource.password: ${{ secrets.MYSQL_PASSWORD }}
application.yml, application-db.yml, application-oauth.yml 로 분리한 파일에 맞도록 set yaml 설정을 바꿔주었다.
- name: Set yaml file
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.RESOURCE_PATH }}
env:
jwt.secret: ${{ secrets.JWT_SECRET }}
- name: Set yaml file for DB
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.DB_RESOURCE_PATH }}
env:
spring.datasource.url: ${{ secrets.MYSQL_DATABASE }}
spring.datasource.username: ${{ secrets.MYSQL_USER }}
spring.datasource.password: ${{ secrets.MYSQL_PASSWORD }}
spring.data.redis.host: ${{ secrets.REDIS_HOST }}
- name: Set yaml file for OAUTH2
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.OAUTH_RESOURCE_PATH }}
env:
spring.security.oauth2.client.registration.naver.client-secret: ${{ secrets.NAVER_CLIENT_SECRET }}
spring.security.oauth2.client.registration.google.client-secret: ${{ secrets.GOOGLE_CLIENT_SECRET }}
spring.security.oauth2.client.registration.kakao.client-secret: ${{ secrets.KAKAO_CLIENT_SECRET }}
spring.security.oauth2.client.registration.github.client-secret: ${{ secrets.CLIENT_SECRET_GITHUB }}
완료
발생한 문제를 하나씩 해결하였더니 결국 CI/CD 구성을 완료하였다.

이제 같이 진행중이었던 다른 레포지토리에도 CI/CD구성을 해두어야겠다.
+ spring docs도 적용해야하는데 다음 레포지토리에서 부터 설정을 해둘 것 같다.
+ 테스트코드 작성에서 항상 막막함을 느끼는데 spring Docs를 잘 쓰려면 습관화 해야겠다는 생각이 든다…
Share article