Home CI/CD 실전
Post
Cancel

CI/CD 실전

S3 사용자 설정

Github Actions 에서 사용할 IAM 사용자를 추가해주어야 한다.
GitHub Acions 에서 AWS 에 접근을 할때는 당연하게 권한이 필요하다.
이런 권한을 IAM 사용자로 추가하여 생성해줄 것 이다.

1. AWS의 IAM ➝ 사용자 ➝ 사용자 생성

image

2. 사용자 세부 정보 지정

image

3. 권한 설정

1. AWSCodeDeployFullAccess

image

2. AmazonS3FullAccess

image

4. 사용자 액세스 키 만들기

iam ➝ 사용자 ➝ 사용자 선택 ➝ 보안 자격 증명 ➝ 액세스 키 만들기

1. 액세스키 이동

image

2. 액세스 키 모범 사례 및 대안 선택

image

3. 태그 설정

image

4. 액세스 키 생성 완료

image

생성된 액세스 키는 잘 보관해야 한다.

Github Actions 세팅

1. 액세스 키 세팅

Settings ➝ Secrets ➝ Actions ➝ New repository secret image

2. appspec.yml

github repository의 루트 디렉토리 최상단에 appspec.yml을 위치하게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: 0.0
os: linux

files:
  - source: / # 인스턴스에 복사할 디렉터리 경로
    destination: /home/ubuntu # 인스턴스에서 파일이 복사되는 위치
    overwrite: yes # 복사할 위치에 파일이 있는 경우 대체

permissions:
  - object: / # 권한이 지정되는 파일 or 디렉터리
    pattern: "**" # 매칭되는 패턴에만 권한 부여
    owner: ubuntu # object의 소유자
    group: ubuntu # object의 그룹 이름

hooks:
  ApplicationStart: # CodeDeploy의 ApplicationStart 단계에서 실행
    - location: deploy.sh
      timeout: 60
      runas: ubuntu 

3. deploy.sh

deploy.sh 파일은 배포를 위해 실행되는 파일이다.
flow를 살펴보자면 먼저 green 버전이 현재 실행 중이라 가정한다.

  1. green 버전이 현재 실행 중인지 확인
  2. green 버전이 현재 실행 중이라면 blue 버전 up
  3. 30초간 blue, green 버전 동시 실행
  4. 30초 후 blue 버전 확인 4-1. if blue 버전 실행 O ➝ green 버전 종료 4-2. if blue 버전 실행 X ➝ blue 버전에 런타임 에러 발생했으므로 slack 알람 전송
    blue 버전을 실행하려 하다가 런타임 에러가 발생해 blue 버전이 내려가고 기존 green 버전이 남아있는 상황이 발생할 수 있다. 따라서 이 경우에는 slack으로 알림을 전송


git repository의 scripts 디렉터리 안에 작성해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 작업 디렉토리를 /home/ubuntu으로 변경
cd /home/ubuntu

# 환경변수 DOCKER_APP_NAME 설정
DOCKER_APP_NAME=ec2-application

# 실행 중인 blue가 있는지 확인
# 프로젝트의 실행 중인 컨테이너를 확인하고, 해당 컨테이너가 실행 중인지를 EXIST_BLUE 변수에 저장
EXIST_BLUE=$(sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)

# 배포 시작한 날짜와 시간을 기록
echo "배포 시작 일자 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

# green이 실행 중이면 blue up
# EXIST_BLUE 변수가 비어있는지 확인
if [ -z "$EXIST_BLUE" ]; then

  # 로그 파일(/home/ubuntu/deploy.log)에 "blue up - blue 배포 : port:8081"이라는 내용을 추가
  echo "blue 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

	# docker-compose.blue.yml 파일을 사용하여 seniors-blue 프로젝트의 컨테이너를 빌드하고 실행
	sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d --build

  # 30초 동안 대기
  sleep 30

  # blue가 문제없이 배포되었는지 현재 실행 여부를 확인
  BLUE_HEALTH=$(sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)

  # blue가 현재 실행 중이지 않다면 -> 런타임 에러 또는 다른 이유로 배포가 되지 못한 상태
  if [ -z "$BLUE_HEALTH" ]; then
    # slack으로 알람을 보낼 수 있는 스크립트를 실행
    sudo /home/ubuntu/slack_blue.sh
  # blue가 현재 실행되고 있는 경우에만 green을 종료
  else

    # /home/ubuntu/deploy.log: 로그 파일에 "green 중단 시작"이라는 내용을 추가
    echo "green 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

    # docker-compose.green.yml 파일을 사용하여 seniors-green 프로젝트의 컨테이너를 중지
    sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down

    # 사용하지 않는 이미지 삭제
    sudo docker image prune -af

    echo "green 중단 완료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log
  fi

# blue가 실행중이면 green up
else
	echo "green 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log
	sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d --build

  sleep 30

  GREEN_HEALTH=$(sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml ps | grep Up)

  if [ -z "$GREEN_HEALTH" ]; then

      sudo ./slack_green.sh
  else

      # /home/ubuntu/deploy.log: 로그 파일에 "blue 중단 시작"이라는 내용을 추가
      echo "blue 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

      # docker-compose.blue.yml 파일을 사용하여 seniors-green 프로젝트의 컨테이너를 중지
      sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down

      # 사용하지 않는 이미지 삭제
      sudo docker image prune -af

      echo "blue 중단 완료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log
  fi
fi

4. Java with Gradle (gradle.yml)

image

Configure를 누른 후 아래와 같이 작성해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
name: Seniors CICD(deploy)

on:
  push:
    branches: [ "master" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v3
        
      # JDK를 17 버전으로 세팅
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      # Gradle 캐싱-> 빌드 속도 UP
      - name: Gradle caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: $-gradle-$
          restore-keys: |
            $-gradle-    

      # application.yml 파일 생성
      - name: make application.yaml
        run: |
          cd ./src/main/resources
          touch ./application.yml
          echo "$" > ./application.yml
        shell: bash

      # Gradle로 빌드 실행
      - name: Build with Gradle
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          arguments: build  

      # AWS에 연결
      - name: Connect to AWS
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: $
          aws-secret-access-key: $
          aws-region: $

      # 빌드파일을 ZIP 파일로 생성
      - name: Make zip file
        run: |
          mkdir deploy
          cp ./appspec.yml ./deploy/
          cp ./Dockerfile ./deploy/
          cp ./docker-compose.blue.yml ./deploy/
          cp ./docker-compose.green.yml ./deploy/
          cp ./deploy.sh ./deploy/
          cp ./build/libs/*.jar ./deploy/
          zip -r -qq -j ./seniors-build.zip ./deploy

      # S3에 zip 파일 업로드
      - name: Upload to S3
        run: |
          aws s3 cp \
            --region ap-northeast-2 \
            ./seniors-build.zip s3://ci-cd-github-actions-s3-bucket 

      # CodeDeploy에 배포 요청
      - name: Code Deploy Deployment Request
        run: |
          aws deploy create-deployment --application-name my-cdoedeply-app \
            --deployment-config-name CodeDeployDefault.OneAtATime \
            --deployment-group-name my-codedeploy-deployment-group \
            --s3-location bucket=ci-cd-github-actions-s3-bucket,bundleType=zip,key=seniors-build.zip

      # 배포 결과 Slack 알람 전송
      - name: Slack 알람 발송
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_COLOR: $ # or a specific color like 'good' or '#ff00ff'
          SLACK_MESSAGE: 배포 결과 => $
          SLACK_TITLE: 배포 결과 알람
          SLACK_USERNAME: Yeti-Bot
          SLACK_WEBHOOK: $
        if: always()

여기서 주의 할 것이, 코드를 읽으며 만든 S3 이름과, CodeDeploy 이름, 그룹을 작성해줘야한다.

  • --application-name : application name (codedeploy에서 만든 이름)
  • deployment-group-name : codedeploy 그룹 명
  • --s3-location bucket : bucket 명
  • s3://ci-cd-github-actions-s3-bucket : bucket 명 그리고 aws-region: $에는 ap-northeast-2를 작성해주었다.

5. APPLICATION 추가

application.yml에 작성할 코드를 작성해주면 된다. Git Repository ➝ Actions ➝ Secrets and variable

1
2
3
4
5
6
7
8
9
10
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: <aws db url>
    username: <name>
    password: <password>
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

Docker

1. docker, docker compose 설치

1. docker 설치

1
2
$ sudo apt update
$ sudo apt install docker.io

2. docker 시작

1
$ sudo systemctl start docker

3. docker-compose 설치

1
 sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

4. docker-compose 권한 부여

1
sudo chmod +x /usr/local/bin/docker-compose

5. docker-compose 권한 확인

1
2
$ docker-compose version
Docker Compose version v2.23.3

docker-compose.yml 작성

Docker compose는 여러 개의 컨테이너로부터 이루어진 서비스를 구축, 실행하는 순서를 자동으로 해서, 관리를 간단히 하는 기능이다.


blue, green 무중단 배포를 구성하므로 docker-compose.blue.yml와 docker-compose.green.yml 파일을 따로 작성한다.
blue 버전은 8081, green 버전을 8082로 설정한다.


프로젝트의 Dockerfile과 같은 곳에 만들어주면 된다.

docker-compose.blue.yml

1
2
3
4
5
6
7
version: '3'
services:
 backend:
   build: .
   ports:
     - "8081:8080"
   container_name: seniors-blue

docker-compose.green.yml

1
2
3
4
5
6
7
version: '3'
services:
 backend:
   build: .
   ports:
     - "8082:8080"
   container_name: seniors-green

Dockerfile

dockerfile은 image를 빌드하기 위한 파일이며 이 image를 기반으로 컨테이너가 실행된다.
아래 코드는 Java 17을 기반으로 하는 애플리케이션을 컨테이너 내에서 실행하기 위한 것으로, 빌드된 애플리케이션 JAR 파일을 Docker 이미지 안에 포함하고 그 JAR 파일을 실행하는 구성을 하고 있다.

1
2
3
4
FROM openjdk:17
ARG JAR_FILE=<프로젝트명>-0.0.1-SNAPSHOT.jar
COPY $JAR_FILE app.jar
ENTRYPOINT ["java","-jar","/app.jar"]


Nginx

1. Nginx 연결

1. ec2 접속

1
ssh -i [pem 키] username@[ip 주소]

2. nginx 설치

1
$ sudo apt install nginx

3. nginx 시작

1
$ sudo systemctl start nginx

4. nginx 상태 확인(초록불 정상)

1
$ sudo systemctl status nginx

image

5. nginx 설정 파일 수정

1
$ sudo vi /etc/nginx/nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# nginx.conf

# Nginx 이벤트 모듈 설정.
events {}

# HTTP 요청과 관련된 설정을 하는 부분.
http {

    # upstream 서버 그룹을 정의. 로드 밸런싱을 위해 여러개의 서버를 가리킬 수 있음.
    upstream spring-server {
        least_conn;
        # 로드 밸런싱을 위해 locahost의 8081 포트와 8082 포트에 동작하는 서버를 지정.
        server localhost:8081 max_fails=3 fail_timeout=10s;
        server localhost:8082 max_fails=3 fail_timeout=10s;
    }

    # 실제 HTTP 서버를 설정하는 부분.
    server {
        # listen 지시문은 서버가 80포트에서 들어오는 요청을 수신하도록 설정.
        listen 80;
        server_name [my domain name]

        include /etc/nginx/default.d/*.conf;

        # 모든 경로에 대한 처리를 정의. 프록시 서버로의 요청을 설정하는 부분.
        location / {
            # proxy_set_header는 프록시 서버로 전달되는 요청 헤더를 설정하는 역할.
            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는 실제 요청을 전달할 upstream 서버 그룹을 지정.
            proxy_pass http://spring-server;

            # proxy_redirect는 프록시 응답의 리다이렉션을 설정하는 부분.
            proxy_redirect off;
        }
    }
}

6. nginx 재시작

1
2
$ sudo service nginx restart
$ sudo service nginx status

Slack

1. 앱 생성

1. slack앱에서 앱 관리로 이동

image

2. 사용자 지정 통합 앱에서 수신 웹 후크 클릭 후 slack에 추가

image

3. 원하는 채널 선택 후 작성

image

발급받은 URL을 Github Actions의 secrets에 추가해준다.

2. 알림 설정

1. Github Actions에 URL 추가

image

URL을 SLACK_WEBHOOK_URL에 넣어준다.

2. ec2 서버안에 작성

/home/ubuntu(deploy.sh에서 작업 디렉토리를 정해준 곳) 안에 작성해준다.

  • slack_blue.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# slack-web-hook URL 세팅
slack_web_hook="........."

# 배포 중 문제가 발생했다는 내용의 로그를 남겨준다.
echo "blue 배포 중 문제 발생 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log
echo "관리자 알람 발송 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

# 슬랙으로 보낼 메시지를 변수에 저장해준다.
json="{ \"text\": \"blue  배포 중 문제가 발생하여 배포가 비정상 중단되었으니 확인 부탁드립니다 -> 문제 발생 시각: $(date '+%Y-%m-%d %H:%M:%S')\" }"

# 변수에 메시지가 잘 입력되었는지 콘솔 창에 출력해본다.
echo "json: $json"

# 슬랙으로 메시지를 발송한다.
curl -X POST -H 'Content-type: application/json' --data "$json" "$slack_web_hook"

# 슬랙 알람 발송 이후 배포 비정상종료 로그를 남겨준다.
echo "관리자 알람 발송 완료, 배포 비정상종료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log
  • slack_green.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# slack-web-hook URL 셋팅
slack_web_hook="....."

# 배포 중 문제가 발생했다는 내용의 로그를 남겨준다.
echo "green 배포 중 문제 발생 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log
echo "관리자 알람 발송 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

# 슬랙으로 보낼 메시지를 변수에 저장해준다.
json="{ \"text\": \"green  배포 중 문제가 발생하여 배포가 비정상 중단되었으니 확인 부탁드립니다 -> 문제 발생 시각: $(date '+%Y-%m-%d %H:%M:%S')\" }"

# 변수에 메시지가 잘 입력 되었는지 콘솔 창에 출력해본다.
echo "json: $json"

# 슬랙으로 메시지를 발송한다.
curl -X POST -H 'Content-type: application/json' --data "$json" "$slack_web_hook"

# 슬랙 알람 발송 이후 배포 비정상종료 로그를 남겨준다.
echo "관리자 알람 발송 완료, 배포 비정상종료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ubuntu/deploy.log

4. 권한 주기

slack_blue.sh, slack_green.sh 모두 권한을 줘야 한다. sudo chmod +wx slack_blue.sh
sudo chmod +wx slack_green.sh

에러 확인

에러가 나 슬랙으로 에러가 전송되어 온다면 (blue 배포 중 문제가 발생하여 배포가 비정상 중단되었으니 확인 부탁드립니다 -> 문제 발생 시각: 2023-12-04 07:03:46)

1
2
$ sudo docker ps -a
$ sudo docker logs <container ID>

위 명령어로 에러 로그를 확인하면 된다.

참고