git/github actions

github actions를 활용한 CICD. 이번에는 spring boot로! (2)

최쌈장 2023. 9. 3. 22:25

 

사실 저번에 올렸던 CICD는 오류가 많은 설정이었다. 왜냐하면 CI를 못하기 때문이다. (테스트를 통과하지 못하는 코드라는 의미이다.) 언젠가 꼭 고쳐놔야겠다고 생각했는데 짬이 나서 코드를 좀 수정했다.

 

대체적으로 CI를 위해서 고민한 내용은 다음과 같다.

1. .env 파일을 어떻게 적용시킬 수 있을까? 
2. Redis, MongoDB와 같은 기자재를 workflow 내에서 어떻게 연결시킬 수 있을까?
3. Test 용 서버를 만들고 싶은데 어떻게 배포할 수 있을까?

 

 

첫번째 이슈

  1. .env 파일을 어떻게 적용 시킬 수 있을까?

 

1) 1차 시도 : actions>secrets 에 하나씩 넣는 방법

  • 너무 비효율적이라고 느꼈음
  • 심지어 yml 파일도 길어지기에 추가적으로 변수가 추가 될때마다 secret 키와 2개의 코드를 추가해야 하는데 이 방법은 번거로웠다.

 

  • gradle.yml 파일
- name: Create .env file
      run: |
        echo "$MONGODB_URI" >> .env
        echo "$REDIS_URI" >> .env
        .....


      env:
        REDIS_URI: ${{secrets.REDIS_URI}}
        MONGODB_URI: ${{secrets.MONGODB_URI}}
       ...

 

 

2) 2차 시도 : json으로 한꺼번에 넣어서 보관함

  • .env 파일을 json 파일로 만들어서 key value 로 쪼개어 나눠서 .env로 만들겠다는 생각
  • 해당 방법은 원래 .env 를 바꿔야 한다는 번거로움이 있기 때문에 변경하기로 함

 

  • gradle.yml
- name: Create .env file
      run: |
        jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env
      env:
        SECRETS_CONTEXT: ${{ toJson(secrets) }}

해당 코드를 통해서 SECRETS_CONTEXT에 json 파일을 모두 갖다놓고, 해당 파일을 key=value 값으로 .env로 놓겠다는 것이다

 

 

 

3) 3차 시도 : 그냥 .env 파일을 아예 secrets 키로 넣어버리기로 함

  • .env 파일 자체를 secrets 키에 보관하기로 함
  • gradle.yml
- name: Create .env file
      run: |
        echo "$SECRETS_CONTEXT" >> .env
      env:
        SECRETS_CONTEXT: ${{secrets.SECRETS_CONTEXT}}
 

매우 깔끔하고 정렬된 코드. 이걸로 결정했다! 게다가 .env 파일을 그대로 복붙해서 넣어주기만 하면 되니 관리하기도 편하다.

 

 

두번째 이슈

 2. Redis와 DB와 같은 기자재는 어떻게 처리할 수 있을까?

 

1) MongoDB는 h2 형태로 내부로 저장할 수 있는 DB가 있는데, 이걸로 해결 가능했다.

 

2) Redis

 

2-1 ) Elasticache와 직접 연동
Redis는 AWS의 elasticache와 연동할 수도 있겠지만, 그러면 inbound 규칙을 추가해 줘야 하는데 이는 보안의 위험이 있다. 또, MONGO DB와 다르게 test 코드에서 redis의 db를 나눠주지 않았기 직접 사용하는 것에는 데이터 관리 면에서도 좋지 않다고 판단.

 

 

2-2 ) Docker container로 Redis 생성
work flow에 container로 redis를 생성하고, test 할 때에 http://127.0.0.1:6379 라는 endpoint로 연동할 수 있도록 한다. 해당 방법이 적합한 것 같아서 선택했다.

  • gradle.yml
jobs:
  build:
    runs-on: ubuntu-latest
    services:
      # Label used to access the service container
      redis:
        # Docker Hub image
        image: redis
        ports:
          # Opens tcp port 6379 on the host and service container
          - 6379:6379

이렇게 jobs를 build 할 때 step를 시작하기 이전에 Redis를 container로 만들고 6379 port로 접근할 수 있도록 열어준다.

 

 

 

세번째 이슈

 3. Test 용 서버를 만들고 싶은데 어떻게 배포할 수 있을까?

 

현재 우리 github actions는 CICD라는 브랜치에 pr을 보내게 되면 서버에 자동으로 배포되게 된다. 하지만 Test Server에 자동적으로 만들고 싶다는 요청을 받았다.

 

 

나는 해당 요청에 다른 branch에다가 pr을 날리게 되면 다른 port에다가 배포될 수 있도록 하는 것이 좋겠다고 생각했다.

 

그래서 CICD일때는 8080포트로, test-Server 라는 branch에 pr을 날릴 경우 8081로 가게 된다.

deploy:
    if: ${{ github.ref == 'refs/heads/CICD' }}
    needs: build
    name: Deploy
    runs-on: [ self-hosted, label-jaribean ]
    # label-jaribean 라는 이름으로 AWS EC2 가 Runner 를 작동시킬 때 사용했던 그 label
    steps:
      - name: Login to ghcr
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GH_TOKEN }}
      - name: Docker run
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASS }}
          docker stop ${{ env.NAME }} && docker rm ${{ env.NAME }} && docker rmi ${{ env.DOCKER_IMAGE }}:latest
          aws s3 cp s3://jaribean-env/spring/.env ~/spring_env/
          docker pull ${{ env.DOCKER_IMAGE }}:latest
          docker create -p 8080:8080 --name ${{ env.NAME }} ${{ env.DOCKER_IMAGE }}:latest
          docker cp ~/spring_env/.env ${{ env.NAME }}:/
          docker start ${{ env.NAME }}
          
  deploy_test:
    if: ${{ github.ref == 'refs/heads/test_server' }}
    needs: build
    name: Deploy_test
    runs-on: [ self-hosted, label-jaribean ]
    # label-jaribean 라는 이름으로 AWS EC2 가 Runner 를 작동시킬 때 사용했던 그 label
    steps:
      - name: Login to ghcr
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GH_TOKEN }}
      - name: Docker run
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASS }}
          docker stop ${{ env.NAME }} && docker rm ${{ env.NAME }} && docker rmi ${{ env.DOCKER_IMAGE }}:latest
          aws s3 cp s3://jaribean-env/spring/.env ~/spring_env/
          docker pull ${{ env.DOCKER_IMAGE }}:latest
          docker create -p 8081:8080 --name ${{ env.NAME }} ${{ env.DOCKER_IMAGE }}:latest
          docker cp ~/spring_env/.env ${{ env.NAME }}:/
          docker start ${{ env.NAME }}

# 참고로 맨 위의 test-server에 pr을 받을 때 실행되야 한다는 코드를 작성해줘야 한다.

 

 

 

 

결론

- 전체 코드

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle 

on:
  push:
    branches: [ "main" , "CICD", "test_server"]

env:
  DOCKER_IMAGE: chlrltjd5263/jaribean
  VERSION: ${{ github.sha }}
  NAME: jaribean

permissions:
  contents: read




jobs:

  build:
    runs-on: ubuntu-latest

    services:
      # Label used to access the service container
      redis:
        # Docker Hub image
        image: redis
        ports:
          # Opens tcp port 6379 on the host and service container
          - 6379:6379

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
    - name: Gradle Caching
      uses: actions/cache@v3
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          ${{ runner.os }}-gradle-
      
    - name: Setup docker buildx
      id: buildx
      uses: docker/setup-buildx-action@v1
        
    - name: Cache docker layers
      uses: actions/cache@v2
      with:
        path: /tmp/.buildx-cache
        key: ${{ runner.os }}-buildx-${{ env.VERSION }}
        restore-keys: |
          ${{ runner.os }}-buildx-
        
    - name: Create .env file
      run: |
        echo "$SECRETS_CONTEXT" >> .env
      env:
        SECRETS_CONTEXT: ${{secrets.SECRETS_CONTEXT}}
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      run: |
        ./gradlew build
    - uses: actions/upload-artifact@v3
      with:
        name: Package
        path: build/libs
        
    - name: Build with Gradle
      run: |
        cp ./build/libs/jariBean-0.0.1-SNAPSHOT.jar app.jar
        ls -R
    
    - name: Build Docker image
      if: contains(github.ref, 'CICD')
      run: |
        ls
        docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASS }}
        docker build -t ${{ env.DOCKER_IMAGE }}:latest .
        docker push ${{ env.DOCKER_IMAGE }}:latest


        
  deploy:
    if: ${{ github.ref == 'refs/heads/CICD' }}
    needs: build
    name: Deploy
    runs-on: [ self-hosted, label-jaribean ]
    # label-jaribean 라는 이름으로 AWS EC2 가 Runner 를 작동시킬 때 사용했던 그 label
    steps:
      - name: Login to ghcr
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GH_TOKEN }}
      - name: Docker run
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASS }}
          docker stop ${{ env.NAME }} && docker rm ${{ env.NAME }} && docker rmi ${{ env.DOCKER_IMAGE }}:latest
          aws s3 cp s3://jaribean-env/spring/.env ~/spring_env/
          docker pull ${{ env.DOCKER_IMAGE }}:latest
          docker create -p 8080:8080 --name ${{ env.NAME }} ${{ env.DOCKER_IMAGE }}:latest
          docker cp ~/spring_env/.env ${{ env.NAME }}:/
          docker start ${{ env.NAME }}
          
  deploy_test:
    if: ${{ github.ref == 'refs/heads/test_server' }}
    needs: build
    name: Deploy_test
    runs-on: [ self-hosted, label-jaribean ]
    # label-jaribean 라는 이름으로 AWS EC2 가 Runner 를 작동시킬 때 사용했던 그 label
    steps:
      - name: Login to ghcr
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GH_TOKEN }}
      - name: Docker run
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASS }}
          docker stop ${{ env.NAME }} && docker rm ${{ env.NAME }} && docker rmi ${{ env.DOCKER_IMAGE }}:latest
          aws s3 cp s3://jaribean-env/spring/.env ~/spring_env/
          docker pull ${{ env.DOCKER_IMAGE }}:latest
          docker create -p 8081:8080 --name ${{ env.NAME }} ${{ env.DOCKER_IMAGE }}:latest
          docker cp ~/spring_env/.env ${{ env.NAME }}:/
          docker start ${{ env.NAME }}

 

 

 

- test 를 성공한 모습

 

- 배포가 완료된 모습

 

 

 

 

 

 

느낀 점

CICD의 세계는 멀고도 험했다. 이걸 나중에는 jenkins로 해야 한다니 쉽지 않겠지만 하나하나 하다보면 할 수 있겠지...

 

파이팅!