Devops/Github Actions

[Github Actions] AWS + Spring Boot CICD 구축 방법

Say simple 2023. 2. 1. 23:41
728x90
반응형

서비스를 배포할 인스턴스를 생성하고 CodeDeploy 동작을 인식하기 위한 태그를 추가합니다.

  • 인스턴스 시작 클릭

  • Amazon Linux 선택

  • 인스턴스 유형, 키 페어(로그인에 사용될 키, 새로 생성 했으면 꼭!!! 로컬에 따로 저장해야 함), 네트워크 설정

  • 네트워크 설정(나중에 필요에 따라 보안 규칙 수정), 스토리지 30GB로 설정(30GB까지 무료)

  • 인스턴스에서 우클릭 한 후 태그 관리 클릭

  • 행동을 식별할 수 있는 태그 추가(값은 입력하지 않아도 됨). Github Actions 스크립트에서 이 태그를 이용해 Ec2를 식별하고 Runner를 실행시킴.

IAM에서 사용자, 역할 추가, 사용자의 키를 Github Actions에 등록

CodeDeploy에 사용될 IAM 작업자를 생성하고 인스턴스에 적용할 역할을 생성합니다.

  • IAM 메뉴에서 사용자 클릭 후 사용자 추가 클릭

  • 이름 입력, 액세스 키 선택 후 다음:권한 버튼 클릭

  • 기존 정책 직접 연결 클릭 후
  • AmazonS3FullAccess
  • AWSCodeDeployFullAccess
  • 권한 선택 후 다음:태그 클릭

  • 태그는 생성하지 않아도 됨, 다음: 검토 클릭

  • 아래와 같이 설정 되었다면 사용자 만들기 클릭, 사용자 생성 완료!

  • 사용자가 생성된 후에 Access key ID와 Secret access key는 꼭 로컬 혹은 안전한 위치에 따로 저장해두도록 하자. 이 페이지를 지나가면 다시는 볼 수 없다.

  • 깃헙 레포지토리로 이동한 후 Settings 클릭, Secrets → Actions 탭에서
  • New Repository secret을 클릭해서 아래 값들을 추가해준다.
  • AWS_ACCESS_KEY_ID: IAM 사용자 Access key ID
  • AWS_SECRET_ACCESS_KEY: IAM 사용자 Secret access key
  • AWS_REGION: 현재 AWS의 인스턴스가 위치한 region 값 (예: ap-northeast-2)

  • IAM에서 역할 클릭 후 역할 만들기 클릭

  • 다른 AWS 서비스의 사용 사례에서 CodeDeploy 검색해서 선택 후 CodeDeploy 라디오 박스 선택, 다음 버튼 클릭

  • 아래 화면과 동일하면 다음 클릭

  • 수정할 것 없이 역할 생성 클릭, 역할 생성 완료!

  • instance에 우클릭 후 보안 → IAM 역할 수정 클릭

  • 미리 생성한 IAM 역할 선택해준 뒤 IAM 역할 업데이트 클릭

S3 생성

Github Actions에서 만든 zip 파일을 업로드 할 S3 Bucket을 생성합니다. 버킷 이름은 AWS 리전 안에서 고유하므로 서비스 이름과 연관 지어서 짓습니다.

  • S3에서 버킷 만들기 선택(s3이름은 고유하므로 가림)

  • 아래 모든 퍼블릭 엑세스 차단이 선택되어 있는지 확인한 후 다른 설정 수정할 것 없이 버킷 만들기 클릭, S3 버킷 생성 완료!

Github Actions에 Runner 등록 및 Ec2에 추가

  • 레포지토리로 이동해서 Settings의 탭에서 Actions → Runners 클릭, New self-hosted runner 클릭

  • 리눅스 클릭한 후 Ec2에 접속해 아래 명령어를 차례대로 실행
  • $ echo "~~~~~~ actions-runner-linux-x64-2.299.1.tar.gz" | shasum -a 256 -c 명령어는 해시 확인하는 명령어이므로 스킵해도 됨(aws linux에 shasum 라이브러리가 없어서 실행 불가능, 외부 다른 방법으로 실행해서 확인해도 됨)

  • ./run.sh 생성 시 4가지 항목이 뜸.
  • 엔터, 엔터, This runner will have the following labels에서 ec2에 부여해준 태그를 추가, 엔터
  • nohup ./run.sh & 입력해서 러너가 백그라운드에서 돌게 해줌
  • Runner 생성 완료!

CodeDeploy 설정

S3에서 코드를 가져와서 Ec2에 배포하기 위해서 CodeDeploy를 설정해줍니다.

  • CodeDeploy의 애플리케이션 탭에서 애플리케이션 생성 버튼 클릭

  • 애플리케이션 이름을 입력하고 EC2/온프레미스 선택, 애플리케이션 생성 클릭

  • 생성한 애플리케이션 클릭

  • 배포 그룹 생성 클릭

  • 배포 그룹 이름 입력, 사전에 생성해준 서비스 역할 선택

  • 환경 구성을 선택한 후 Amazon EC2 인스턴스 클릭, Ec2에 생성해준 태그를 입력

  • 사전에 설정한 로드 밸런서가 없다면 배포 그룹 생성 버튼 클릭, CodeDeploy 설정 완료!

sudo yum update
sudo yum install ruby
sudo yum install wget
cd /home/ec2-user
wget <https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install>
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent status
  • "error: No AWS CodeDeploy agent running"와 같은 메시지가 표시되면 서비스를 시작하고 다음 두 명령을 한 번에 하나씩 실행합니다.
sudo service codedeploy-agent start
sudo service codedeploy-agent status

Github Actions 배포 스크립트 설정

Github에서 정해진 동작(push, pull request)이 발생했을 때 실행될 스크립트를 작성합니다.

  • 레포지토리의 Actions 탭에서 New workflow 버튼 클릭

  • set up a workflow yourself 클릭

  • 아래와 같이 스크립트 입력(스크립트 파일 아래에 있음)

 

Github Action으로 CI/CD 구축하기 - 4편 : deploy.yaml 분석

Index

goodgid.github.io

 

  • env는 스크립트에서 동적으로 설정한 변수
  • S3_BUCKET_NAME: S3 버킷의 이름을 그대로 입력
  • CODE_DEPLOY_APPLICATION_NAME: CodeDeploy 이름을 그대로 입력
  • CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: CodeDeploy 배포 그룹 이름을 그대로 입력
  • deploy의 runs-on에 Ec2에 등록해준 태그를 입력
  • docker-compose 등의 키워드를 모른다면 docker 페이지에서 학습하고 오기
  • 아래 스크립트는 Spring Boot의 예시
name: CI-CD prod

on:
  push:
    branches: [main]

env:
  S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
  CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.AWS_CODEDEPLOY_NAME }}
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.AWS_CODEDEPLOY_GROUP_NAME }}

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source code
        uses: actions/checkout@master

      - 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: Make .env
        run: |
          touch ./.env
          echo "${{ secrets.DOTENV }}" > ./.env
        shell: bash

      - name: Make application.yml
        run: |
          cd ./src/main/resources
          touch ./application.yml
          echo "${{ secrets.APPLICATION_YML }}" > ./application.yml
        shell: bash

      - name: Make schema.sql
        run: |
          mkdir ./src/main/resources/db
          cd ./src/main/resources/db
          touch ./schema.sql
          echo "${{ secrets.SCHEMA_SQL }}" > ./schema.sql
        shell: bash

      - name: Make data.sql
        run: |
          cd ./src/main/resources/db
          touch ./data.sql
          echo "${{ secrets.DATA_SQL }}" > ./data.sql
        shell: bash

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle

        run: |
          ./gradlew build

      # 수정
      - name: Make zip file
        run: zip -r ./$GITHUB_SHA.zip .
        shell: bash

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Upload to S3
        run: aws s3 cp --region $AWS_REGION ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip

      # 추가
      - name: Code Deploy
        run: |
          aws deploy create-deployment \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip

  run:
    needs: deploy # build 후에 실행되도록 정의
    runs-on: [admin-inspection-api-prod] # AWS ./configure에서 사용할 label명

    steps:
      - uses: actions/checkout@v3
      - name: Run deploy.sh
        run: |
          cd /home/ec2-user/orderhero-admin-inspection-api
          sudo chmod 777 ./deploy.sh
          sudo chmod 777 ./down.sh
          sudo chmod 777 ./logging.sh
          ./deploy.sh
  • 배포하기 위한 deploy.sh 생성
#!/bin/bash

cd /home/ec2-user/orderhero-admin-inspection-api

DOCKER_APP_NAME=spring

# 실행중인 blue가 있는지
EXIST_BLUE=$(docker ps | grep spring-blue)
# 실행중인 nginx가 있는지
EXIST_NGINX=$(docker ps | grep nginx)
# 실행중인 postgres가 있는지
EXIST_NGINX=$(docker ps | grep postgres)
# 실행중인 kafka가 있는지
EXIST_KAFKA=$(docker ps | grep kafka)

health_check() {
	RESPONSE=$(curl -s http://127.0.0.1:$2)
	# 헬스 체크
	echo "$3 health check: $IDLE_PORT count: $1... "
	echo "response: $RESPONSE"
	if [ -n "$RESPONSE" ]; then
		echo "$3 down"
		docker-compose -p ${DOCKER_APP_NAME}-$3 -f docker-compose.green.yml down
		docker image prune -af # 사용하지 않는 이미지 삭제
		echo "$3 down complete"
		exit
	fi
	sleep 3
}

# nginx 콘테이너가 없으면 빌드
if [ -z "$EXIST_NGINX" ]; then
	docker-compose -p nginx -f docker-compose.nginx.yml up --build -d
fi

# postgres 콘테이너가 없으면 빌드
if [ -z "$EXIST_PG" ]; then
	docker-compose -p postgres -f docker-compose.postgres.yml up --build -d
fi

# kafka 콘테이너가 없으면 빌드
if [ -z "$EXIST_KAFKA" ]; then
	rm -rf ./logs/kafka1
	rm -rf ./logs/kafka2
	docker-compose -p kafka -f docker-compose.kafka.yml up --build -d
fi

# green이 실행중이면 blue up
if [ -z "$EXIST_BLUE" ]; then
	echo "blue up"
	docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d --build
	IDLE_PORT=8081
	echo "blue up complete"

	for RETRY_COUNT in {1..20}; do
		health_check $RETRY_COUNT $IDLE_PORT "green"
	done

	echo "Failed to health check. Please check docker container is running."

# blue가 실행중이면 green up
else
	echo "green up"
	docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d --build
	IDLE_PORT=8082
	echo "green up complete"

	for RETRY_COUNT in {1..20}; do
		health_check $RETRY_COUNT $IDLE_PORT "blue"
	done

	echo "Failed to health check. Please check docker container is running."
fi
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/dl-orderhero-settlement
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

위의 환경 변수들은 아래와 같이 설정한다.

  • 깃헙 레포지토리 홈페이지에서 settings > secrets and variables > actions 클릭

  • New repository secret 클릭

  • Name에 키 이름, Secret에 값 입력

아래 yml 파일들은 모두 스프링 프로젝트의 루트 폴더에 만들어준다.

  • docker-compose.blue.yml
# docker-compose.yml
version: "3.9"

#blue
services:
  api:
    container_name: spring-blue
    # restart: always
    build: .
    ports:
      - "8081:8080"
    volumes:
      - ./logs:/app/logs
  • docker-compose.green.yml
# docker-compose.yml
version: "3.9"

#green
services:
  api:
    container_name: spring-green
    # restart: always
    build: .
    ports:
      - "8082:8080"
    volumes:
      - ./logs:/app/logs
  • Dockerfile
FROM openjdk:17

WORKDIR /app

COPY ./build/libs/inspection-0.0.1-SNAPSHOT.jar app.jar

ENTRYPOINT ["java","-jar","app.jar"]
  • docker-compose.nginx.yml
version: "3.9"

services:
  nginx:
    container_name: nginx
    restart: always
    image: nginx:1.15
    volumes:
      - ./nginx/default.prod.conf:/etc/nginx/conf.d/default.conf
    ports:
      - 80:80
      - 443:443
  • default.prod.conf
upstream backend {
    # ip_hash;
    least_conn;
    server localhost:8081 weight=1 max_fails=1 fail_timeout=3s;
    server localhost:8082 weight=1 max_fails=1 fail_timeout=3s;
    server 172.17.0.1:8081 weight=1 max_fails=1 fail_timeout=3s;
    server 172.17.0.1:8082 weight=1 max_fails=1 fail_timeout=3s;
}

server {
    listen 80;
    listen [::]:80;
    listen 443;
    server_name localhost;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    # if ($http_x_forwarded_proto != 'https') {
    #         return 301 https://$host$request_uri;
    # }

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header HOST $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://backend/;
        proxy_redirect off;
    }
}
  • docker-compose.postgres.yml
version: "3.9"

services:
  postgres:
    container_name: postgres
    image: postgres:15.1-alpine
    restart: always
    ports:
      - 5432:5432
    volumes:
      - ./postgresql/data:/var/lib/postgresql/data
    env_file:
      - ./.env

스크립트의 양이 많고, deploy.sh를 어떻게 짜느냐에 따라서 헬스 체크, 커넥션 체크 등 추가적으로 로직이 들어갈 수 있는 만큼 양이 많고 복잡하지만 각 스크립트를 확인하면서 단계적으로 적용하면 어려움 없이 적용할 수 있다.

728x90
반응형