Overview

애플리케이션을 개발하면 외부에서도 접근 가능하도록 클라우드 환경에 배포합니다.

이전에 포스팅 했던 AWS 1편에서는 마지막에 scp 명령어로 로컬에 존재하는 빌드 파일을 EC2 인스턴스로 복사한 후 ssh 로 접속해서 실행시켰습니다.

하지만 매 배포마다 이렇게 하면 굉장히 번거롭고 실수할 가능성도 높아집니다.

그래서 이런 수작업을 자동화하는 여러 가지 툴과 기법들이 등장했고 Github Actions 도 그 중 하나입니다.

Github Actions 에 대해서는 지난 포스팅 에서 한번 다룬 적이 있습니다.

이번에는 Github Actions 를 사용해서 AWS EC2 에 자동으로 배포하는 과정을 알아봅니다.

글은 다음과 같은 순서로 진행됩니다.

  1. Github Actions 에서 AWS 에 배포하는 방법
  2. AWS EC2 설정 추가
  3. AWS S3 버킷 생성
  4. AWS CodeDeploy 앱 생성 및 배포 설정
  5. Github Actions 에서 사용할 사용자 권한 추가
  6. AppSpec 파일 작성
  7. 배포 스크립트 작성
  8. Github Actions Workflow 작성
  9. Github 에서 push 로 배포하기

1. 배포 방법

main 브랜치에 Push 하면 자동으로 EC2 까지 배포되는 Workflow 를 만들어봅시다.

먼저 Workflow 를 작성하기 전에 어떤 방식으로 EC2 까지 배포가 이루어지는 지 전체적인 플로우를 알아야 합니다.

Github Actions 를 확인하면 CI 과정에서 했던 것처럼 aws.yml 이라는 기본 Workflow 를 제공합니다.

배포하는 방법은 여러 가지 있겠지만 AWS 의 경우 큰 흐름은 하나입니다.

소스 코드를 압축하여 AWS 스토리지에 저장 후 서버에 전달해서 실행한다

그리고 AWS 에서 공식적으로 가이드하는 방법은 크게 두 가지가 있습니다.

  1. AWS S3 빌드파일 압축해서 업로드 -> AWS EC2 배포 (CodeDeploy 활용)
  2. AWS ECR 에 도커 이미지 업로드 -> AWS ECS 배포 (Task Definition 활용)

1.1. 어떤 차이가 있을까?

우리는 EC2 인스턴스에 배포해야 하기 때문에 1번 방법을 사용합니다.

Github Actions 에서 제공하는 AWS Workflow 는 2번 방법을 안내하고 있어서 차이점을 먼저 알아봤습니다.

AWS ECR 은 Docker Image 를 저장하는 레지스트리고 AWS ECS 일종의 도커 컨테이너 서비스입니다.

AWS ECS 는 미리 정의한 Task Definition 을 기반으로 클러스터에 인스턴스를 생성하고 ECR 에 저장된 도커 이미지를 배포하는 등 인스턴스를 관리하며 스케일 인/아웃을 지원합니다.

여러 서버 인스턴스를 관리하기 위해선 더 편할 수 있으나 우리는 이미 존재하는 EC2 인스턴스에 배포하는 걸 목적으로 하기 때문에 1번 방법을 사용합니다.


1.2. 배포 과정

큰 흐름을 요약하면 다음과 같습니다.

  1. Github Actions 에서 코드 빌드 (테스트는 CI 에서 했다고 검증했다고 판단하여 생략)
  2. AWS 인증
  3. 코드 압축해서 AWS S3 에 업로드
  4. AWS CodeDeploy 실행하여 S3 에 있는 코드 EC2 에 배포

결국 Github 프로젝트 코드를 AWS S3 에 업로드 한 후 AWS EC2 에서 끌어다 쓰는 것이 가장 핵심이며 AWS CodeDeploy 는 그걸 보조해주는 역할을 담당합니다.

Github 에서 CodeDeploy, S3 에 접근하기 위한 권한이 필요하고 EC2 에서 S3 에 접근하기 위한 권한도 필요하기 때문에 설정이 조금 복잡하게 느껴질 수도 있습니다.

차근차근 진행 해보겠습니다.


2. EC2 설정 추가

AWS EC2 편 에서 생성한 인스턴스를 기준으로 아래 작업들을 추가로 진행합니다.

  1. Tag 추가 (CodeDeploy 에서 어떤 인스턴스에 실행할 지 구분하는 값)
  2. IAM 역할 등록
  3. EC2 서버에 CodeDeploy Agent 설치

2.1. Tag 추가

CodeDeploy 를 생성할 때 어떤 인스턴스에서 수행할 지 구분하는 값으로 태그를 사용하기 때문에 추가가 필요합니다.

기존에 인스턴스 생성할 때 태그까지 같이 생성했다면 이 과정은 생략해도 괜찮습니다.


2.1.1. EC2 설정에서 태그 관리 선택

EC2 인스턴스 정보에 들어가 태그 관리를 선택합니다.


2.1.2. 태그 추가

원하는 키 값을 입력하고 저장을 누릅니다.


2.1.3. 태그 확인

다시 EC2 인스턴스 정보에서 태그가 등록되었는지 확인할 수 있습니다.


2.2. IAM 역할 추가

EC2 인스턴스에서 S3 에 올려놓은 파일에 접근할 수 있도록 권한을 추가해줘야 합니다.


2.2.1. IAM 역할 관리 페이지로 이동

기본적으로 존재하는 역할들이 있는데 신경쓰지 말고 새로운 역할 만들기를 선택합니다.


2.2.2. EC2 엔티티 선택

IAM 역할을 연결할 서비스를 선택합니다.


2.2.3. S3 접근 권한 추가

EC2 인스턴스에서 S3 접근할 수 있도록 AmazonS3FullAccess 권한을 추가합니다.


2.2.4. 이름 설정

마지막으로 원하는 이름을 입력한 뒤 생성을 완료합니다.


2.2.5. EC2 인스턴스에서 IAM 연결

EC2 인스턴스 관리 페이지로 이동해서 "작업 > 보안 > IAM 역할 수정" 을 선택합니다.


방금 만든 EC2 전용 IAM 역할을 선택한 뒤 저장을 누르면 연결이 완료됩니다.


2.3. CodeDeploy Agent 설치

$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
$ cd /home/ubuntu
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto > /tmp/logfile
$ sudo service codedeploy-agent status

CodeDeploy Agent 설치 를 보고 명령어를 따라 치기만 하면 됩니다.

EC2 환경이 Ubuntu 가 아니거나 버전이 다르다면 공식 문서를 참고해주세요.


정상적으로 설치가 완료되면 이런 응답이 와야 합니다.


3. AWS S3 생성

AWS S3 버킷이란 이미지 또는 zip 파일을 저장하기 위한 스토리지 서비스입니다.

빌드한 프로젝트 코드를 압축해서 S3 에 저장한 후 EC2 서버에서 S3 에 접근해서 압축한 파일을 가져오기 위해 사용합니다.


3.1. S3 메뉴에서 버킷 생성

S3 메뉴로 이동해서 "버킷 만들기" 를 누릅니다.


3.2. 일반 구성과 객체 소유권 설정

원하는 버킷 이름과 리전을 선택합니다.

ACL 은 기본값을 선택해서 비활성화 합니다.


3.3. 액세스, 버킷 버전, 암호화 비활성화

나머지 설정을 마저 합니다.

변경할 필요 없이 기본값 그대로 두면 됩니다.


3.4. S3 버킷 생성 완료

버킷 생성이 완료되면 이렇게 나타납니다.


4. CodeDeploy 생성

배포를 도와주는 CodeDeploy 생성 및 설정을 진행해봅니다.


4.1. CodeDeploy 전용 IAM 역할 만들기

CodeDeploy 를 사용하기 위해선 IAM 에서 역할을 만들어야 합니다.


4.1.1. IAM 메뉴에서 역할 선택

IAM 서비스로 이동해서 역할 만들기를 선택합니다.


4.1.2. CodeDeploy 엔티티 선택

기본적으로 제공되는 AWS 서비스에서 CodeDeploy 를 검색한 후 가장 기본적인 걸 선택합니다.


4.1.3. IAM 이름 설정

나머지는 건들 필요 없고 이름만 새로 추가합니다.

저는 my-codedeploy-iam 로 설정했습니다.

이름을 입력했다면 "역할 생성" 을 눌러 마무리합니다.


4.2. CodeDeploy 애플리케이션 생성

이제 우리가 사용할 CodeDeploy 앱을 생성합니다.


메뉴에서 생성 버튼을 누릅니다.


원하는 이름을 입력 후 컴퓨팅 플랫폼은 EC2/온프레미스 를 선택합니다.


4.3. CodeDeploy 배포 그룹 생성

CodeDeploy 애플리케이션에서 사용하는 배포 그룹을 생성합니다.


4.3.1. 메뉴에서 선택

방금 만든 애플리케이션에서 배포 그룹 생성을 누릅니다.


4.3.2. 이름, 역할, 유형 선택

원하는 배포 그룹 이름, 역할, 유형을 설정합니다.

서비스 역할은 위에서 만든 IAM 역할을 선택할 수 있게 나옵니다.


4.3.3. EC2 인스턴스 선택

어떤 인스턴스에서 동작할 지 선택합니다.

EC2 인스턴스에서 태그를 추가해야 선택할 수 있습니다.

우리는 위에서 이미 기존 EC2 인스턴스에 태그를 추가했기 때문에 해당 태그 키를 선택합니다.


4.3.4. 나머지 설정 후 배포 그룹 생성

AWS Systems Manager 는 크게 중요한거 같지 않으니 적당히 선택하고 로드 밸런싱을 사용하지 않으니 체크만 해제합니다.

다 설정했으면 배포 그룹 생성을 눌러 마무리합니다.


5. Github Actions 에서 사용할 IAM 사용자 추가

AWS 를 Github Actions 워크 플로우에서 접근하려면 권한이 필요합니다.

지금까지는 IAM 역할만 추가해서 특정 서비스 (EC2, CodeDeploy) 에게 부여 했지만 이번에는 IAM 사용자를 추가해봅니다.


5.1. IAM 사용자 메뉴로 이동

IAM 메뉴에서 사용자 추가를 선택합니다.


5.2. IAM 사용자 이름 및 액세스 유형 설정

사용자 이름을 추가하고 액세스 유형을 선택합니다.

우리는 Github Actions 에서 사용해야 하기 때문에 암호 방식 대신 액세스 키 방식을 선택합니다.


5.3. 접근이 필요한 권한 추가

이 사용자에게 추가할 접근 권한을 고릅니다.

워크 플로우에서 CodeDeploy 를 실행해야 하기 때문에 다음 두 권한을 추가합니다.

  • AWSCodeDeployFullAccess
  • AmazonS3FullAccess

5.4. 사용자 만들기 완료

태그는 필요 없기 때문에 생략하고 잘못된 설정이 없는지 마지막으로 확인 후 사용자를 만듭니다.


5.5. Access Key 및 Secret Key 확인

사용자를 만들고 나면 "액세스 키 ID" 와 "비밀 액세스 키" 가 존재합니다.

이 두 개의 키 값을 사용해서 IAM 권한을 획득할 수 있습니다.

우리는 이걸 Github Actions 에서 사용할 수 있도록 등록합니다.


5.6. Github Repository 의 Secrets 추가

Github Actions 을 적용하려는 Github > Repository > Settings > Secrets 로 이동해서 위 키 값들을 등록합니다.

키 이름은 적당히 편한 것으로 설정합니다.

Github Secrets 에 저장한다고 해도 값을 직접 확인할 수 없기 때문에 필요한 경우 따로 저장해둡니다.


6. AppSpec 파일 작성

지금까지 우리는 서버를 띄울 EC2, 배포할 결과물을 저장할 S3, 배포를 도와줄 CodeDeploy 이렇게 총 세 가지 AWS 서비스를 만들었습니다.

이제 CodeDeploy 에서 배포를 위해 참조할 AppSpec 파일을 작성합니다.

AppSpec 파일을 사용해서 우리는 프로젝트의 어떤 파일들을 EC2 의 어떤 경로에 복사할지 설정 가능하고, 배포 프로세스 이후에 수행할 스크립트를 지정하여 자동으로 서버를 띄울 수도 있습니다.

AppSpec 파일은 기본적으로 루트 디렉터리에 위치해야 합니다.


version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu

전체 파일은 위와 같으며 각 섹션별로 조금씩만 살펴보겠습니다.


6.1. files 섹션

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes

배포 파일에 대한 설정입니다.

  • source: 인스턴스에 복사할 디렉터리 경로
  • destination: 인스턴스에서 파일이 복사되는 위치
  • overwrite: 복사할 위치에 파일이 있는 경우 대체

AppSpec "files" 섹션 문서를 참고하면 더 자세한 내용을 알 수 있습니다.


6.2. permissions 섹션

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

files 섹션에서 복사한 파일에 대한 권한 설정입니다.

  • object: 권한이 지정되는 파일 또는 디렉터리
  • pattern (optional): 매칭되는 패턴에만 권한 부여
  • owner (optional): object 의 소유자
  • group (optional): object 의 그룹 이름

AppSpec "permissions" 섹션 문서를 참고하면 더 자세한 내용을 알 수 있습니다.


6.3. hooks 섹션

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu

배포 이후에 수행할 스크립트를 지정할 수 있습니다.

일련의 라이프사이클이 존재하기 때문에 적절한 Hook 을 찾아 실행할 스크립트를 지정하면 됩니다.

위 코드에서는 파일을 설치한 후 AfterInstall 에서 기존에 실행중이던 애플리케이션을 종료시키고 ApplicationStart 에서 새로운 애플리케이션을 실행합니다.

  • location: hooks 에서 실행할 스크립트 위치
  • timeout (optional): 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨
  • runas (optional): 스크립트를 실행하는 사용자

AppSpec "hooks" 섹션 문서를 참고하면 더 자세한 내용을 알 수 있습니다.


7. 배포 스크립트 작성

바로 위 AppSpec Hooks 에서 실행할 스크립트 stop.shstart.sh 를 설정했습니다.

이제 수행할 스크립트 파일을 작성합니다.


7.1. stop.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"

DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)

# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
  echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
  echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
  kill -15 $CURRENT_PID
fi

애플리케이션이 이미 떠있으면 종료하는 스크립트입니다.

간단히 주석으로 설명을 달아두었으니 쉘 스크립트를 작성할 줄 안다면 보기에 어려움은 없을 겁니다.


7.2. start.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"

APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE

# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &

CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG

애플리케이션을 실행하는 스크립트입니다.

Github Actions 워크플로우에서 이미 빌드는 마쳤기 때문에 JAR 파일만 복사 후 실행합니다.


7.3. build.gradle 파일 수정

위 스크립트를 보면 /build/libs/*.jar 파일을 $JAR_FILE 파일로 복사합니다.

그런데 Spring Boot 2.5 버전부터는 빌드 시 일반 jar 파일 하나와 -plain.jar 파일 하나가 함께 만들어집니다.

그래서 빌드 시 plain jar 파일은 만들어지지 않도록 build.gradle 파일에 다음 내용을 추가해야 합니다.

jar {
    enabled = false
}

build.gradle.kts 파일은 이렇게 작성하시면 됩니다.

tasks.getByName<Jar>("jar") {
    enabled = false
}

8. Github Actions Workflow 작성

이제 필요한 사전 작업은 모두 끝났으니 Github Actions 워크 플로우만 작성하면 됩니다.


8.1. Sample Workflow 선택

Github Actions CI 편에서는 기본적으로 제공되는 gradle 샘플을 수정했지만 배포 플로우는 거의 다 수정해야 하므로 그냥 가장 심플한 워크 플로우를 선택합니다.


8.2. deploy.yml 파일 작성

name: Deploy to Amazon EC2

on:
  push:
    branches:
      - main

# 본인이 설정한 값을 여기서 채워넣습니다.
# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: my-github-actions-s3-bucket
  CODE_DEPLOY_APPLICATION_NAME: my-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: my-codedeploy-deployment-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
    # (1) 기본 체크아웃
    - name: Checkout
      uses: actions/checkout@v3

    # (2) JDK 11 세팅
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        distribution: 'temurin'
        java-version: '11'

    # (3) Gradle build (Test 제외)
    - name: Build with Gradle
      uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
      with:
        arguments: clean build -x test

    # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
    - 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_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

    # (5) 빌드 결과물을 S3 버킷에 업로드
    - name: Upload to AWS S3
      run: |
        aws deploy push \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --ignore-hidden-files \
          --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
          --source .

    # (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
    - name: Deploy to AWS EC2 from S3
      run: |
        aws deploy create-deployment \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

전체 파일은 위와 같으며 스텝 별로 간단한 주석을 달아두었습니다.

결국 프로젝트를 빌드한 후 AWS S3 버킷에 푸시 후 CodeDeploy 를 수행하는 겁니다.

(4), (5), (6) 에 대해서만 간략한 설명을 덧붙이겠습니다.


8.2.1. (4) AWS 인증

# (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
- 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_ACCESS_KEY }}
    aws-region: ${{ env.AWS_REGION }}

AWS 에 접근하기 위해 인증하는 스텝입니다.

우리는 위에서 IAM 사용자를 만든 후 Access Key, Secret Key 를 Github 레포에 저장했습니다.

그러면 secrets 변수를 통해 우리가 저장한 키 값들을 가져와서 사용할 수 있습니다.

필요한 Access Key, Secret Key 등을 프로젝트 코드에 노출시키지 않은 채로 사용할 수 있다는 편리함이 있습니다.


8.2.2. (5) AWS S3 에 업로드

# (5) 빌드 결과물을 S3 버킷에 업로드
- name: Upload to AWS S3
  run: |
    aws deploy push \
      --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
      --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
      --ignore-hidden-files \
      --source .

원하는 파일들을 압축해서 AWS S3 에 업로드 하는 스텝입니다.

공식 문서를 참고하면 더 자세한 정보를 알 수 있습니다.

  • --application-name: CodeDeploy 애플리케이션 이름
  • --s3-location: 압축 파일을 업로드 할 S3 버킷 정보
  • --ignore-hidden-files (optional): 숨겨진 파일까지 번들링할지 여부

$GITHUB_SHA 라는 변수가 보이는데 간단하게 생각해서 Github 자체에서 커밋마다 생성하는 랜덤한 변수값입니다. (자세한 정보는 Github Context 참조)

이렇게 랜덤한 값을 사용하면 파일 업로드 시에 이름 중복으로 충돌날 일이 없습니다.


8.2.3. (6) AWS EC2 에 배포

# (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
- name: Deploy to AWS EC2 from S3
  run: |
    aws deploy create-deployment \
      --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
      --deployment-config-name CodeDeployDefault.AllAtOnce \
      --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
      --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

위 스텝에서 S3 에 저장한 파일을 EC2 에서 땡겨온 후 압축을 풀고 스크립트를 실행합니다.

공식 문서를 참고하면 더 자세한 정보를 알 수 있습니다.

  • --application-name: CodeDeploy 애플리케이션 이름
  • --deployment-config-name: 배포 방식인데 기본값을 사용
  • --deployment-group-name: CodeDeploy 배포 그룹 이름
  • --s3-location: 버킷 이름, 키 값, 번들타입

9. Github Actions 사용해서 배포하기

이제 모든 세팅이 끝났으므로 배포를 진행해봅니다.

yaml 파일로 설정한 것처럼 main 브랜치에 push 되는 경우 Github Actions Workflow 가 수행됩니다.


9.1. Github Repo 에서 확인

워크 플로우가 정상적으로 수행되면 이렇게 커밋에 체크 표시가 생깁니다.


"Details" 를 눌러보거나 "Actions" 탭으로 이동하면 수행한 스텝이 나옵니다.

만약 실패한다면 어떤 스텝에서 실패했는지 확인할 수 있습니다.


9.2. CodeDeploy 에서 배포 내역 확인

AWS CodeDeploy 메뉴로 이동하면 배포 내역을 확인인할 수 있습니다.


9.3. EC2 서버에서 애플리케이션 실행 확인

EC2 서버에 접속해서 확인 해보면 Spring Boot 프로젝트 코드가 있으며 서버도 정상적으로 떠있는 것을 확인할 수 있습니다.

  • CodeDeploy 배포 로그 확인: /var/log/aws/codedeploy-agent/codedeploy-agent.log
  • CodeDeploy Hooks 로그 확인: /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log

Reference

'공부 > CI & CD' 카테고리의 다른 글

Github Actions CI: 자동 빌드 및 테스트 하기  (0) 2022.04.27
AWS 2편: RDS 생성 후 EC2 와 연동  (2) 2022.01.23
AWS 1편: EC2 생성 후 Spring Boot 띄우기  (7) 2022.01.21
Docker 명령어  (0) 2022.01.05
kubectl 명령어  (0) 2021.12.23

Overview

한 프로젝트를 여러 사람이 개발할 때 코드의 안정성은 굉장히 중요합니다.

환경 변수 변경, 비즈니스 로직 수정, Git 충돌 등 여러 사람들이 코드를 공유하면서 발생할 수 있는 문제점은 굉장히 많습니다.

실수를 방지하기 위해 테스트 코드를 작성하지만 매 PR 리뷰 때마다 각 리뷰어들이 일일히 테스트 코드를 돌려보며 리뷰하면 생산성이 저하됩니다.

단순히 리뷰 요청자의 테스트 잘 돌아갑니다~ 같은 코멘트보다 테스트의 성공을 확실히 보장해주는 수단이 필요합니다.

그래서 CI/CD 라는 개념이 등장했습니다.


1. CI/CD

  • CI (Continuous Integration)
    • 해석하면 "지속적 통합" 으로 여러 개발자가 하나의 프로젝트를 같이 개발할 때 발생하는 불일치를 최소화 해주는 개념입니다.
    • CI 를 제대로 구현하면 애플리케이션 변경 사항 반영 시 자동으로 빌드 및 테스트 되어 잘못된 코드가 공유되는 걸 방지합니다.
  • CD (Continuous Deployment)
    • "지속적 배포" 라는 뜻으로 프로젝트의 변경 사항을 가상 환경에 자동으로 배포하는 것을 의미합니다.
    • CD 를 구성해두면 변경 사항을 배포할 때 사용하는 파이프라인을 공유하여 번거로움을 없앨 수 있습니다.

좀더 자세한 내용은 RedHat 공식문서를 참고하세요.

쉽게 표현하자면 CI 는 자동 빌드 및 테스트를 진행하여 여러 개발자들이 공유하는 코드의 신뢰성을 높이는 개념이고 CD 는 배포 플로우를 자동화하여 누구나 동일한 플로우로 배포할 수 있게 만들어주는 개념입니다.


2. Github Actions

Github Actions 는 Github 에서 제공하는 CI/CD 툴입니다.

build, test, deploy 등 필요한 Workflow 를 등록해두면 Gihtub 의 특정 이벤트 (push, pull request) 가 발생했을 때 해당 워크 플로우를 수행합니다.

예를 들어 Pull Request 를 올리면 자동으로 해당 코드의 테스트를 수행하여 수행한다던지 master branch 에 코드를 push 하면 자동으로 코드를 배포하는 등 여러 가지 반복적인 작업을 자동으로 수행해줍니다.

Jenkins, Circle CI, Travis CI 등 다른 후보들도 있지만 Github Actions 은 별다른 툴을 설치하지 않아도 Github Repository 에서 바로 사용할 수 있다는 장점이 있습니다.


3. Github Actions 자동 빌드 및 테스트

Github Actions 를 사용한 CI 환경을 구축해봅니다.

Pull Request 를 올렸을 때 자동으로 빌드 및 테스트를 수행하여 코드의 품질을 검사합니다.


3.1. Workflow 선택

Github Repository 로 이동해서 Actions 탭에서 새로운 Workflow 를 추가할 수 있습니다.

자주 사용되는 언어나 프레임워크는 이미 제공되는게 있기 때문에 검색해서 선택만 하면 됩니다.

Configure 버튼을 누르면 기본적으로 제공되는 gradle.yml 파일이 제공됩니다.


3.2. gradle.yml 파일 수정

name: Java CI with Gradle

on:
  pull_request:
    branches: [ main ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:

    # 1) 워크플로우 실행 전 기본적으로 체크아웃 필요
    - uses: actions/checkout@v3

    # 2) JDK 11 버전 설치, 다른 JDK 버전을 사용한다면 수정 필요
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'

    # 3) Gradle 사용. arguments 를 붙이면 뒤에 그대로 실행된다고 생각하면 됨
    # 이 워크플로우는 gradle clean build 를 수행함
    - name: Build with Gradle
      uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
      with:
        arguments: clean build

기본적으로 제공되는 파일에서 조금 수정했습니다.

크게 신경써야 해줄 부분은 on 부분과 jobs 부분입니다.

on 에서는 워크플로우를 수행할 이벤트를 결정합니다.

위 코드는 main 을 베이스 브랜치로 한 Pull Request 를 생성하였을 때 수행된다는 뜻입니다.

jobs 에서는 수행할 워크플로우를 차례대로 입력하면 됩니다.

어떤 동작인지 간단하게 주석으로 표현했으며 더 궁금한 점은 Github Actions Docs 를 참고해주세요.


3.3. Pull Request 작성

이제 PR 을 작성하면 자동으로 Github Actions 가 동작하여 빌드를 실행합니다.


3.3.1. Workflow 실패

Github Actions 이 실패하면 이렇게 실패했다고 알려줍니다.

여러 워크 플로우를 한번에 수행시킬 수도 있으며, Github 설정에 따라 워크 플로우가 성공하지 않으면 머지 불가능 하도록 제한할 수도 있습니다.


3.3.2. Workflow 성공

모든 워크플로우가 성공하면 이렇게 녹색으로 성공 여부를 알려줍니다.

코드를 리뷰하는 사람은 직접 코드를 돌려보지 않아도 빌드와 테스트가 성공한다는 사실을 알 수 있습니다.


Conclusion

Github Actions 의 가장 큰 이점은 Github 과의 연계성이라고 생각합니다.

대부분의 프로젝트가 Github 저장소를 활용하고 있기 때문에 별다른 세팅 없이도 쉽게 이용할 수 있습니다.

이번 포스팅 내용에서 좀더 나아가 Workflow 에서 테스트 커버리지를 파악해서 알려주는 라이브러리도 존재합니다.

커버리지가 일정 기준을 넘지 않으면 머지 불가능하게 만들어 테스트 코드 작성을 강제할 수도 있습니다.

다음에는 Github Actions 를 사용해 AWS EC2 에 배포하는 과정을 작성해볼 예정입니다.

'공부 > CI & CD' 카테고리의 다른 글

Github Actions CD: AWS EC2 에 Spring Boot 배포하기  (24) 2022.05.05
AWS 2편: RDS 생성 후 EC2 와 연동  (2) 2022.01.23
AWS 1편: EC2 생성 후 Spring Boot 띄우기  (7) 2022.01.21
Docker 명령어  (0) 2022.01.05
kubectl 명령어  (0) 2021.12.23

Overview

지난 포스팅에서는 AWS 에서 EC2 인스턴스를 생성하고 Spring Boot 서버를 띄워 외부에서 요청하는 것까지 해봤습니다.

이번에는 데이터베이스 연동을 위해 RDS 인스턴스를 생성하고 이전에 만든 EC2 와 연동하는 것까지 진행해봅니다.

다음과 같은 순서로 진행됩니다.

  • RDS 인스턴스 생성
  • 보안 그룹 설정
  • RDS 접속 테스트
  • 파라미터 그룹 설정

1. RDS 인스턴스 생성

EC2 서버에 DB 연동을 하기 위한 RDS 인스턴스를 생성해봅니다.


1.1. RDS 메뉴로 이동

EC2 와 마찬가지로 검색하면 쉽게 찾을 수 있습니다.


1.2. 데이터베이스 생성

대시보드에서 선택해도 되고 아니면 이렇게 메뉴에 진입해서 직접 데이터베이스 생성을 눌러도 됩니다.


1.3. DB 종류 선택

저는 MySQL 을 선택했습니다.


1.4. DB 설정 입력

데이터베이스 이름, 마스터 이름, 비밀번호를 입력합니다.

실제 DB 에 접근할 때 사용할 정보이므로 신중하게 입력해야 합니다.


1.5. 스토리지 설정

사실 프리 티어일 때는 선택권이 거의 없습니다.

그냥 대부분 기본 설정을 사용하면 되는데 "스토리지 자동 조정" 저 부분 체크만 해제해주시면 됩니다.

안그러면 개발을 진행하다 임계값이 초과되면 자동으로 스토리지가 늘어나서 과금될 가능성이 있습니다.


1.6. 보안 그룹 설정

퍼블릭 액세스는 "예" 로 지정해줍니다.

"아니요"를 선택하면 퍼블릭 IP 주소가 할당되지 않기 때문에 외부에서 접속할 수 없습니다.

그리고 EC2 와 마찬가지로 보안 그룹을 설정하거나 새로 생성할 수 있습니다.

기존에 사용 중인게 있다면 "기존 항목 선택" 을 누르고 보안 그룹을 추가하면 됩니다.

여기서는 "새로 생성" 으로 해보겠습니다.


1.7. 추가 구성

추가 구성에서 데이터베이스 이름을 적고 자동 백업을 비활성화 합니다.

어차피 개발용이라 데이터가 중요하지 않아서 자동 백업을 비활성화 했지만, 만약 지워져도 복구해야 하는 데이터라면 당연히 백업을 활성화 해야 합니다.

여기까지 진행했으면 "데이터베이스 생성" 을 눌러서 생성을 완료합니다.

생성 완료까지는 시간이 좀 걸립니다.


2. RDS 보안 그룹 설정

RDS 인스턴스를 생성할 때 보안 그룹을 새로 생성한 걸 기억하실 겁니다.

데이터베이스는 서버에서 접근 가능해야 하기 때문에 보안 그룹 설정이 추가로 필요합니다.

여기서 서버란 곧 "EC2 인스턴스의 탄력적 IP" 를 의미합니다.

탄력적 IP 를 직접 넣는 대신 손쉽게 설정할 수 있는 방법이 있습니다.


2.1. 현재 보안 그룹 확인

RDS 인스턴스 정보로 들어가면 하단에서 보안 그룹 규칙을 확인할 수 있습니다.

친절하게 제가 접속할 수 있게 로컬 IP 만 인바운드 규칙에 추가해두었네요.

아웃바운드는 EC2 와 마찬가지로 모든 트래픽에 대해 열어두었습니다.

RDS 보안 그룹은 EC2 보안 그룹이랑 별도로 관리하지 않고 같은 곳에서 관리합니다.

따라서, 보안 그룹을 편집하려면 EC2 대시보드로 이동하거나 저 보안 그룹 이름을 클릭해서 페이지를 열어야 합니다.


2.2. 보안 그룹 리스트에서 EC2 보안 그룹 ID 복사

EC2 대시보드의 보안 그룹 메뉴로 이동하면 지금까지 만들었던 보안 그룹 리스트를 확인할 수 있습니다.

이전에 만들었던 MySecurityGroup 도 있을 텐데 보안 그룹 ID 를 복사해줍니다.


2.3. RDS 인바운드 규칙에 EC2 보안 그룹 ID 입력

유형을 MYSQL/Aurora 로 선택하고 사용자 지정으로 EC2 보안 그룹의 ID 를 추가하고 저장합니다.


3. RDS 접속 테스트

보안 그룹 설정까지 했다면 실제로 연결이 잘 되는지 다음 두 가지를 테스트 해봅시다.

  • 로컬 PC 에서 접속
  • EC2 인스턴스 서버에서 접속

3.1. 엔드포인트와 포트 확인

RDS 인스턴스 정보에서 엔드포인트와 포트를 확인합니다.


3.2. 로컬 PC (Sequel Ace) 에서 접속

Database GUI 툴은 여러 가지가 있으므로 각자 편한걸로 접근하시면 됩니다.

저는 Sequel Ace 에서 접속을 시도해보겠습니다.

아래 세가지만 입력 후 Connect 를 누르면 접속 가능합니다.

  • Host: RDS 엔드포인트
  • Username: RDS 생성 시 입력했던 정보
  • Password: RDS 생성 시 입력했던 정보

RDS 생성 시에 지정했던 초기 데이터베이스 이름도 확인할 수 있네요.


3.3. EC2 에서 접속

먼저 EC2 에 접속해줍니다.

우선 MySQL 을 먼저 설치해줘야 합니다.

# Ubuntu 에서 MySQL 설치
$ sudo apt-get update
$ sudo apt-get install mysql-server

그리고 mysql 명령어로 접속을 시도합니다.

권한 문제가 있으면 sudo 로 재시도 합니다.

# mysql -u {유저이름} -p --host {엔드포인트}
$ mysql -u admin -p --host my-rds-instance.ciweuig9oiko.ap-northeast-2.rds.amazonaws.com

접속이 잘 된것을 확인할 수 있습니다.


4. RDS 파라미터 그룹 설정

이제 추가적으로 파라미터 그룹 설정을 해줍시다.

RDS 는 다음 세가지 설정을 필수로 해줘야 합니다.

  • Time Zone
  • Character Set
  • Max Connection

4.1. 파라미터 그룹 페이지로 이동

먼저 파라미터 그룹 메뉴를 찾아 이동합니다.


4.2. 파라미터 그룹 생성

파라미터 그룹 패밀리는 RDS DB 와 맞춰서 선택하고 이름과 설명만 입력해서 생성합니다.

생성한 파라미터 그룹을 클릭해서 파라미터 편집을 누릅니다.


4.3. Time Zone

타임존을 Asia/Seoul 로 변경합니다.


4.4. Character Set

character_set 으로 검색해서 나온 6 개의 값을 전부 utf8mb4 로 변경해줍니다.

원래는 utf8 을 많이 사용했으나 utf8mb4 가 이모지까지 지원하기 때문에 더 많이 사용되는 추세입니다.


collation 으로 검색해서 나온 값들도 전부 utf8mb4_general_ci 로 변경해줍니다.


4.5. Max Connection

마지막으로 max_connections 을 수정해줍니다.

이 값은 원래 RDS 인스턴스 사양에 의해 결정됩니다.


4.6. 최종 변경사항 정리

저장하기 전에 미리보기를 하면 마지막으로 변경 사항들을 확인할 수 있습니다.


4.7. RDS 파라미터 그룹 변경

RDS 인스턴스로 이동해서 "수정" 버튼을 클릭합니다.

그리고 "추가 구성" 탭으로 이동해서 DB 파라미터 그룹을 변경해줍니다.


RDS 는 인스턴스를 수정할 때 예약 적용과 즉시 적용을 선택할 수 있는데 초기 설정이므로 "즉시 적용" 을 선택해줍시다.


Conclusion

이렇게 해서 EC2 에 인스턴스 연동까지 진행해봤습니다.

Spring Boot 에서 DB 에 연동하고 싶다면 로컬에서 접속한 것처럼 세팅해주고 진행하면 됩니다.

이제 DB 연동한 서버를 외부에 노출하는 것까지 가능합니다.

Overview

AWS EC2 인스턴스를 생성하고 Spring Boot 서버를 띄워보는 것까지 진행합니다.

주 목표는 서버를 외부에 제공하는 거라서 따로 배포 시스템을 구축하지 않고 단순히 빌드 파일을 복사해서 수동으로 띄울 겁니다.

글은 다음과 같은 순서로 진행합니다.

  • EC2 인스턴스 생성
  • 탄력적 IP (Elastic IP) 추가
  • 터미널에서 SSH 클라이언트로 EC2 접속
  • 보안 그룹 설정
  • Spring Boot 서버 띄우기

1. EC2 인스턴스 생성

우선 AWS 홈페이지 에 접속해서 계정을 생성 후 콘솔에 로그인 된 상태여야 합니다.

2022년 1월 16일 기준으로 작성되었으며 이후에 홈페이지 인터페이스가 변경될 수 있습니다.

2022년 4월 29일 기준으로 AWS EC2 인스턴스 생성 UI 가 바뀌어서 이부분을 수정합니다.


1.1. AWS Region 설정

우선 위치를 서울로 설정합니다.

리전에 따라서 인스턴스 위치가 결정되기 때문에 외국으로 하면 속도가 낮을 수도 있습니다.

만약 대한민국이 아닌 다른 나라에 서비스 하려면 그 도시를 선택해도 됩니다.


1.2. EC2 메뉴로 이동

처음에 대시보드가 나올텐데 만약 EC2 서비스를 찾기 힘들다면 검색해서 들어갑니다.


1.3. 새 인스턴스 생성

인스턴스 메뉴로 들어가 인스턴스 시작 버튼을 클릭합니다.


1.4. Amazon Machine Image(AMI) 및 인스턴스 유형 선택

AMI 는 어떤 서버로 구성할지 선택하는 겁니다.

여러 종류가 있어서 원하는 걸 선택하면 되고, 저는 프리 티어에 Ubuntu LTS 버전을 선택했습니다.

인스턴스 유형은 프리 티어를 사용한다면 다른 선택권이 없습니다.

스펙이 좋을수록 과금이 더 많이 되기 때문에 처음부터 좋은걸 고르기보다는 작게 시작했다가 스케일업 해나가는 걸 추천드립니다.


1.5. 키 페어 생성

키 페어는 EC2 서버에 SSH 접속을 하기 위해 필수라서 생성해야 합니다.

"새 키 페어 생성" 을 눌러 원하는 이름을 적고 생성합니다.

위 그림처럼 설정 후 생성하면 자동으로 my-key.pem 파일이 다운되며, SSH 환경에 접속하기 위해서는 해당 키 파일이 존재하는 위치로 가서 ssh 명령어를 실행하면 됩니다.

한번 다운받은 후에는 재다운 받을 수 없기 때문에 안전한 곳에 저장해둡니다.


생성한 후에는 이렇게 키 페어를 선택하면 됩니다.


1.6. 네트워크 및 스토리지 선택

네트워크 설정은 EC2 에 접속을 허용하는 ACL 을 생각하면 됩니다.

나중에 "보안 그룹" 이라는 걸로 별도 설정을 할 예정이기 때문에 SSH 트래픽만 허용해줍니다.

위 설정대로 하면 SSH 트래픽 접속 가능한 IP 가 내 IP 로 자동 설정 됩니다.


프리티어는 최대 30 까지 지원하기 때문에 해당 부분만 변경해줍니다.

볼륨 유형은 범용 SSD 로 선택해야 합니다.

만약 Provisioned IOPS SSD (프로비저닝된 IOPS SSD) 를 선택한다면 사용하지 않아도 활성화한 기간만큼 계속 비용이 발생하게 됩니다.


1.7. 인스턴스 설정 요약

다 설정하면 우측에 간단하게 지금까지 설정한 인스턴스 요약이 뜹니다.

이상한 부분이 없다면 인스턴스 시작을 눌러서 생성하면 됩니다.


1.8. 인스턴스 생성 완료

처음 화면으로 돌아오면 이렇게 인스턴스가 생성된 것을 확인할 수 있습니다.

이제 탄력적 IP 와 보안 그룹을 추가하기 위해 인스턴스 ID 를 클릭합니다.


2. 탄력적 IP (Elastic IP)

AWS EC2 인스턴스는 서버를 중지하고 다시 실행시키면 퍼블릭 IP 가 변경되기 때문에 클라이언트가 사용할 수 있는 변하지 않는 IP 가 필요합니다.

탄력적 IP (Elastic IP) 란 외부에서 인스턴스에 접근 가능한 고정 IP 입니다.

탄력적 IP 는 만들어놓고 사용하지 않더라도 과금이 되기 때문에 필요한 만큼만 생성하는 것이 중요합니다.

자세한 설명은 AWS Docs - 탄력적 IP 에서 확인하실 수 있습니다.


우선, 진짜 퍼블릭 IP 가 변경하는지 한번 확인해봅시다.

현재 생성된 인스턴스의 정보입니다.

퍼블릭 IP 로 3.35.238.69 가 할당된 것을 확인할 수 있습니다.



인스턴스를 중지 시켰다가 다시 실행시켜보았습니다.

인스턴스 ID, 프라이빗 IP 와 같은 정보는 동일한데 퍼블릭 IP 가 13.125.3.218 로 변경된 것을 볼 수 있습니다.

이렇게 퍼블릭 IP 는 변경될 가능성이 있기 때문에 변하지 않는 탄력적 IP 를 할당해주어야 합니다.



2.1. 탄력적 IP 메뉴 접근

메뉴에서 탄력적 IP 를 찾아서 들어갑니다.

아직 아무것도 할당받은 게 없기 때문에 새로운 IP 를 추가합니다.


2.2. 새로운 탄력적 IP 할당

딱히 변경할 건 없고 바로 할당을 선택해서 만들어줍시다.


2.3. 탄력적 IP 주소 선택

방금 생성한 탄력적 IP 를 선택해서 연결을 시도합니다.


2.4. 인스턴스 선택 및 연결

설정 화면에 들어가면 현재 내 인스턴스 목록을 선택할 수 있고 연결된 프라이빗 IP 까지 선택 가능합니다.


2.5. 인스턴스 정보 확인

탄력적 IP 를 연결하고 다시 인스턴스 정보를 확인해보면 IP 가 할당된 것을 볼 수 있습니다.

퍼블릭 IP 주소도 기존 값에서 탄력적 IP 주소로 자동으로 변경되었습니다.


3. SSH 클라이언트로 서버 접속

이제 내가 만든 EC2 인스턴스에 접속해봅니다.

인스턴스 정보에서 "연결" 버튼을 클릭하면 인스턴스에 연결 가능한 여러가지 방법을 알려줍니다.

여기서 "SSH 클라이언트" 로 접속하는 방법을 알아봅니다.


사실 너무 친절하게 알려주고 명령어도 복사할수 있게 되어있어서 따로 할건 없습니다.

저 방법대로 서버에 한번 접속해보고, 호스트를 등록해서 간편하게 등록하는 방법을 알아봅시다.


3.1. 터미널 실행 후 키 페어 파일 위치로 이동

터미널을 실행해서 이전에 다운 받은 키 페어 파일 위치로 이동합니다.

저는 다운로드 받은 후 따로 옮기지 않았기 때문에 Downloads 디렉토리에 들어갔습니다.

키 파일이 없으면 접속할 수 없고 다시 다운받을 수도 없기 때문에 잘 관리해야 합니다.


3.2. 키 파일 권한 변경

$ chmod 400 my-key.pem

키 파일의 권한을 변경해줍니다.


3.3. SSH 접속

가이드에 있는 대로 퍼블릭 DNS 또는 퍼블릭 IP 를 사용해서 인스턴스에 접속할 수 있습니다.

# 퍼블릭 DNS 로 접속
$ ssh -i "my-key.pem" ubuntu@ec2-3-37-206-248.ap-northeast-2.compute.amazonaws.com

# 퍼블릭 IP 로 접속
$ ssh -i "my-key.pem" ubuntu@3.37.206.248



3.4. 호스트 등록해서 간편하게 접속

이제 우리는 SSH 로 EC2 인스턴스 서버에 접속할 수 있습니다.

하지만 위에서 본 것처럼 매번 ssh -i {키 페어 파일} {ubuntu}@{탄력적 IP} 를 입력해야 합니다.

일일히 기억하기도 귀찮고 타이핑도 번거롭기 때문에 호스트로 등록해서 쉽게 접속할 수 있도록 변경해봅시다.


3.4.1. ~/.ssh 디렉터리로 키 페어 파일 복사

우선 키 페어 파일을 ~/.ssh/ 로 복사합니다.

$ cp my-key.pem ~/.ssh/

3.4.2. 키 페어 파일 권한 변경

키 페어 파일의 권한을 변경합니다.

$ chmod 600 my-key.pem



3.4.3. ~/.ssh/config 파일 생성

~/.ssh/ 디렉터리에 config 파일을 생성해서 다음과 같이 입력합니다.

이미 파일이 존재한다면 맨 아래에 입력하면 됩니다.

User 는 우분투를 선택했다면 ubuntu 고 그 외에는 전부 ec2-user 일겁니다.

$ vi ~/.ssh/config

# 아래는 파일 내용
# ssh -i {키 페어 파일} {유저 이름}@{탄력적 IP}
Host {원하는 호스트 이름}
User {유저 이름}
HostName {탄력적 IP}
IdentityFile {키 페어 파일 위치}

위 형식에 따라 저는 다음과 같이 작성했습니다.



3.4.4. 설정한 Host 이름으로 접속

이제 키 페어 파일이 없는 곳에서도 간단한 별칭으로 SSH 접속이 가능합니다.



4. 보안 그룹 설정

보안 그룹은 AWS 에서 제공하는 방화벽 모음입니다.

서비스를 제공하는 애플리케이션이라면 상관 없지만 RDS 처럼 외부에서 함부로 접근하면 안되는 인스턴스는 허용된 IP 에서만 접근하도록 설정이 필요합니다.

  • 인바운드 (Inbound): 외부 -> EC2 인스턴스 내부 허용
  • 아웃바운드 (Outbound): EC2 인스턴스 내부 -> 외부 허용

4.1. 현재 보안 그룹 확인

인스턴스 정보의 보안 탭에서 현재 설정된 보안 그룹을 확인할 수 있습니다.

처음 인스턴스를 생성할 때 아무런 보안 그룹을 설정하지 않았기 때문에 default 값만 들어가있습니다.

인바운드 규칙 해석해보면 22번 포트의 모든 IP 에 대해서 TCP 연결을 허용한다는 의미입니다.

SSH 접속을 위해 22번 포트는 기본으로 설정해준 것 같네요.

이제 새로운 보안 그룹을 만들어 봅시다.


4.2. 보안 그룹 ID 리스트 확인

인스턴스에서 보안 그룹 ID 를 보고 들어갈 수도 있지만 메뉴에서 직접 들어가면 이렇게 보안 그룹 ID 리스트를 볼 수 있습니다.

보안 그룹은 인스턴스와 별개로 존재하기 때문에 한번 만들어두면 새 인스턴스를 생성해도 한번에 적용할 수 있습니다.

우측 상단의 "보안 그룹 생성" 버튼을 눌러서 새 보안 그룹을 만들어봅시다.


4.3. 보안 그룹 생성

먼저 보안 그룹의 이름과 설명을 추가하고 인바운드 규칙과 아웃바운드 규칙을 편집합니다.


4.4. 인바운드 규칙

인바운드는 외부에서 EC2 로 요청할 때 허용할 IP 대역을 설정할 수 있습니다.

원하는 규칙을 추가하려면 "규칙 추가" 버튼을 누릅니다.

유형을 먼저 선택하면 자동으로 그에 맞는 프로토콜과 포트 범위가 고정됩니다.

웬만한 유형들이 이미 존재하기 때문에 편하게 설정할 수 있습니다.

우선 로컬 PC 에서 서버에 접속할 수 있게 SSH 를 추가하고 소스를 내 IP 로 추가합니다.

"소스" 를 선택하면 우측에 알아서 IP 가 추가되며, 특정 IP 나 대역을 넣으려면 "사용자 지정" 으로 추가할 수 있습니다.

만약 여러 사람이 함께 작업하는 프로젝트라면 각각의 로컬 PC IP 를 전부 추가해줘야 합니다.

SSH, HTTP, HTTPS 와 같은 기본 포트들을 열고 스프링 부트 프로젝트트까지 사용자 지정으로 추가해줍니다.


4.5. 아웃바운드 규칙

아웃바운드 규칙은 딱히 설정할 필요 없기 때문에 "모든 트래픽" 그대로 둡니다.


4.6. 인스턴스에서 보안 그룹 변경

다시 인스턴스로 돌아와서 "보안 그룹 변경" 버튼을 눌러줍니다.


방금 생성한 MySecurityGroup 을 추가해줍니다.

보안 그룹은 여러 개를 동시에 설정할 수 있기 때문에 기존에 설정된 launch-wizard-1 보안 그룹은 제거해줍니다.


4.7. 변경된 보안 그룹 확인

다시 인스턴스 설정을 보면 보안 그룹이 적용된 것을 볼 수 있습니다.

만약 제대로 보이지 않는다면 새로고침을 한번 해주세요.


5. EC2 인스턴스에 Spring Boot 서버 띄우기

배포 시스템 이런거 생략하고 진짜 단순하게 서버 띄우는 것만 확인해봅니다.

가장 간단한 방법은 두가지가 있는데 1번으로 진행하겠습니다.

  1. jar 파일을 빌드하여 EC2 복사 후 실행
  2. EC2 에서 프로젝트 git clone 후 실행

5.1. JDK 설치

# EC2 인스턴스
$ sudo apt-get update
$ sudo apt-get install openjdk-11-jdk

java -version 으로 명령어로 설치 여부를 확인할 수 있습니다.


5.2. Spring Boot 프로젝트 빌드

# 프로젝트 파일로 이동 후
$ gradle clean build

BUILD SUCCESSFUL in 5s
17 actionable tasks: 10 executed, 7 up-to-date


# 빌드 파일 복사
$ scp ./build/libs/api-0.0.1-SNAPSHOT.jar {호스트 이름}:/home/ubuntu

프로젝트를 빌드하면 ./build/libs 디렉토리에 jar 파일이 생성됩니다.

해당 파일을 EC2 서버로 복사합니다.

호스트 이름에는 ubuntu@{퍼블릭 IP} 또는 ubuntu@{퍼블릭 DNS} 가 들어가야 하는데 만약 ~/.ssh/config 에 호스트 이름을 등록해두었다면 간소화된 이름을 사용할 수 있습니다.

퍼블릭 IP (탄력적 IP) 또는 퍼블릭 DNS 를 그대로 사용한다면 키 페어 파일 (.pem) 이 명령어를 사용하는 위치에 존재해야 합니다.


5.3. EC2 인스턴스에서 실행

# EC2 인스턴스
$ nohup java -jar api-0.0.1-SNAPSHOT.jar &

nohup 명령어를 사용하면 로그를 nohup.out 파일에 남길 수 있습니다.


5.4. 퍼블릭 IP 또는 DNS 로 접근 확인

http://{탄력적 IP}로 접속하면 정상적으로 서버에 연결이 되는 걸 볼 수 있습니다.


Conclusion

지금까지 AWS EC2 인스턴스를 생성하고, 변하지 않는 탄력적 IP 를 할당해주고, 보안 그룹으로 방화벽을 설정한 후에 서버를 띄우는 것까지 진행해봤습니다.

다음에는 데이터베이스를 관리하는 RDS 인스턴스를 만들어보겠습니다.

'공부 > CI & CD' 카테고리의 다른 글

Github Actions CD: AWS EC2 에 Spring Boot 배포하기  (24) 2022.05.05
Github Actions CI: 자동 빌드 및 테스트 하기  (0) 2022.04.27
AWS 2편: RDS 생성 후 EC2 와 연동  (2) 2022.01.23
Docker 명령어  (0) 2022.01.05
kubectl 명령어  (0) 2021.12.23

1. docker run

$ docker run --name my-container -p 8080:8080 -d my-container-image
  • --name <name>: 도커 컨테이너 이름을 설정
  • -d: 백그라운드로 실행
  • -p <local port>:<container port>
    • 특정 포트로 연결
    • 로컬 머신의 8080 포트를 컨테이너 내부의 8080 포트와 매핑시킴
    • http://localhost:8080 으로 애플리케이션에 접근 가능

컨테이너 이미지로 컨테이너를 실행합니다.


2. docker exec

# my_continaer 컨테이너에 echo 명령어 실행
$ docker exec my_container echo "hello world!"

# my_container 컨테이너의 bash 접속
$ docker exec -it my_container bash
  • -i: 표준 입력(STDIN) 을 오픈 상태로 유지합니다. 셸에 명령어를 입력하기 위해 필요합니다.
  • -t: 의사 (PSEUDO) 터미널 (TTY) 을 할당합니다.

도커 컨테이너에 명령어를 전달하여 실행합니다.

i 옵션을 빼면 명령어를 입력할 수 없고, t 옵션을 빼면 명령어 프롬포트가 화면에 표시되지 않습니다.


3. docker images

$ docker images

도커 이미지 목록을 조회합니다.


4. docker rmi

$ docker rmi my-docker-image

도커 이미지를 삭제합니다.


Reference

1. gcloud (구글 클라우드) Cluster

클러스터는 Node 의 묶음입니다.

클러스터를 전환하면 kubectl get nodes 명령어의 정보도 달라집니다.

# 구글 클라우드 클러스터 리스트 조회
$ gcloud container clusters list

# 구글 클라우드의 클러스터를 kubeconfig 에 등록
$ gcloud container clusters get-credentials <클러스터 이름>

# 클러스터 생성 (노드 3개와 함께)
$ gcloud container clusters create <클러스터 이름> --num-nodes 3

# 구글 클라우드 클러스터 삭제
$ gcloud container clusters delete <클러스터 이름>

2. kubectl config

kubectl 명령어에 클러스터 정보, 인증 정보를 세팅해두고 편하게 사용할 수 있습니다.


2.1. Credential

# 인증 정보 조회
$ kubectl config get-users

# 인증 정보 생성
$ kubectl config set-credentials <이름> --옵션들

# 인증 정보 삭제
$ kubectl config unset users.<이름>

2.2. Cluster

# 클러스터 리스트 조회
$ kubectl config get-clusters

# 클러스터 생성
$ kubectl config set-cluster <클러스터 이름> --server <Host:Port>

# 클러스터 삭제
$ kubectl config unset clusters.<클러스터 이름>

2.3. Context

컨텍스트는 클러스터 정보와 인증 정보를 매핑해서 하나로 관리할 수 있게 해줍니다.

특정 클러스터를 사용하기 위해선 사용자 정보도 필요합니다.

만약 A 유저 정보를 사용하는 A 클러스터와 B 유저 정보를 사용하는 B 클러스터가 존재한다고 생각해 봅시다.

각 클러스터를 스위칭하기 위해선 유저 정보를 또 스위칭하는 과정을 매번 해야 합니다.

Context 는 클러스터와 유저 정보를 하나로 묶어서 컨텍스트를 스위칭 하는 것만으로도 클러스터 접근을 쉽게 할 수 있습니다.

# 컨텍스트 리스트 조회
$ kubectl config get-contexts

# 현재 컨텍스트 조회
$ kubectl config current-context

# 새로운 컨텍스트 생성
$ kubectl config set-context <컨텍스트 이름>

# 특정 컨텍스트 사용 (전환)
$ kubectl config use-context <컨텍스트 이름>

# 특정 컨텍스트 삭제
$ kubectl config unset contexts.<컨텍스트 이름>

2.4. 컨텍스트에 인증 정보와 클러스터를 매핑

# 1. username, password 로 인증 정보 생성
$ kubectl config set-credentials <Credential 이름> --username=<아이디> --password=<비밀번호>

# 2. token 으로 인증 정보 생성
$ kubectl config set-credentials <인증 정보 이름> --token=<토큰>

# 클러스터 정보 생성
$ kubectl config set-cluster <클러스터 이름> --server <Host:Port>

# 클러스터 정보와 인증 정보로 새로운 컨텍스트 생성
$ kubectl config set-context <Context 이름> --cluster=<Cluster 이름>  --user=<Credential 이름>

# 컨텍스트 사용
$ kubectl config use-context account-webapp-dev-context

3. 노드 (Node)

노드는 여러 개의 파드를 갖는 단위입니다.

$ kubectl get nodes

4. 파드 (Pod)

파드는 쿠버네티스의 가장 기본적인 배포 단위입니다.

파드는 하나 이상의 컨테이너를 포함하기 때문에 각 컨테이너를 각각 배포하지 않고 한번에 배포 가능합니다.

파드는 고유의 IP 와 Port 를 가지며, 파드 내의 컨테이너들은 localhost:<각 컨테이너 포트> 형식으로 서로 통신 가능합니다.

# 파드 조회 (-o wide 붙이면 좀더 자세한 정보 나옴) (pods = po)
$ kubectl get pods

# 좀더 자세히 조회
$ kubectl get po -o wide

# 파드 삭제
$ kubectl delete pod kubia

# 파드 전체 삭제
$ kubectl delete po --all

# 파드 상세한 설명 보기
$ kubectl describe pod <파드 이름>

# 로그 보기
$ kubectl logs <파드 이름>

# 여러 컨테이너를 포함한 파드인 경우에 컨테이너 이름 지정
$ kubectl logs <파드 이름> -c  <컨테이너 이름>

# 이전 컨테이너의 로그 확인
$ kubectl logs <파드 이름> --previous

# 파드의 환경변수 확인
$ kubectl exec <파드 이름> -- env

4.1. 새로운 파드 생성 (kubectl create vs apply)

  • create: 새로운 Object 생성. 이미 존재하면 에러
  • apply: 새로운 Obejct 생성. 이미 존재하면 변경된 부분만 반영

https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create


5. 서비스 (Service)

서비스는 외부에서 파드에 접근할 수 있게 해주는 로드 밸런서 역할을 합니다.

파드는 어떠한 이유에 의해 새로 생성되거나 지워질 수 있는데 이럴 때마다 파드의 IP 가 바뀌기 때문에 외부 클라이언트가 파드의 정확한 IP 를 추적하기 어렵습니다.

서비스는 여러 파드를 갖고 있으며 외부에서는 각 파드의 IP, Port 대신 서비스의 IP, Port 에 접근하면 서비스가 알아서 파드로 연결해줍니다.

# 서비스 생성
$ kubectl expose deployment kubia --type=LoadBalancer --port=8080 --name=kubia-http

# 서비스 조회
$ kubectl get svc

6. 레이블 (Label)

이름 그대로 라벨링의 역할을 합니다. (발음은 책마다 다른 것 같네요)

각 파드에 Key:Value 레이블을 설정해서 나중에 "레이블 셀렉터" 라는 걸로 특정 파드들을 동시에 제어할 수 있습니다.

주로 서비스가 목적에 따라 구분된 파드들을 동시에 실행하거나, 삭제하거나, 조회할 때 사용합니다.

# 레이블까지 조회
$ kubectl get po --show-labels

# 레이블 추가 (--overwrite 옵션을 추가하면 기존 레이블 수정)
$ kubectl label po <파드 이름> <레이블>=<설정>

# 특정 레이블들까지 같이 조회
$ kubectl get po -L creation_method=manual,env

# creation_method=manual 에 해당하는 파드 조회
$ kubectl get po -l creation_method=manual

# 값에 상관 없이 env 레이블을 갖고 있는 파드 조회
$ kubectl get po -l 'env'

# 위와 반대로 env 레이블을 갖고 있지 않은 파드 조회
$ kubectl get po -l '!env'

노드에도 파드처럼 레이블 세팅이 가능합니다.

# gpu=true 의 레이블을 노드에 세팅
$ kubectl label node <노드이름> gpu=true

6.1. 레이블 셀렉터 (Label Selector)

등록된 레이블 기반으로 특정 파드들을 그룹화 할 때 사용합니다.

쿼리처럼 사용 가능합니다.

  • creation_method!=manual: creation_method 레이블 갖고 있는 파드 중에 값이 manual 이 아닌 것
  • env in (prod,dev): env 레이블 값이 prod 또는 dev 로 되어 있는 파드
  • env notin (prod,dev): env 레이블 값이 prod, dev 가 아닌 파드

7. 어노테이션 (Annotation)

파드에 붙이는 주석 같은 개념입니다.

PR 번호, 작성자 등등.. 레이블처럼 키 값으로 사용할 수는 없지만 Description 역할을 합니다.

# 특정 파드에 어노테이션 추가
$ kubectl annotate pod <파드이름> mycompany.com/someannotation="foo bar"

8. 네임스페이스 (namespace)

클러스터를 논리적으로 구분하는 가상 클러스터라고 생각하면 됩니다.

Pod, Service 등을 네임스페이스 별로 생성하거나 관리할 수 있고 사용자의 권한 역시 별도로 부여할 수 있습니다.

처음 공부할 때는 레이블이랑 뭐가 다른건지 헷갈렸었는데..

레이블은 말 그대로 파드에 붙은 정보를 의미하는 거고 네임스페이스는 다음과 같은 일을 할 수 있습니다.

  • 특정 네임스페이스에 별도의 권한을 두어 파드, 서비스 등 접근 분리
  • dev, sandbox, prod 처럼 애플리케이션은 동일하지만 환경이 다른 경우 각 환경 별로 리소스를 다르게 할당 가능 (prod 환경은 좀더 많이 할당)

위에서 한번 언급했지만 네임스페이스는 논리적으로 구분하는 것이지 물리적인 구분은 아니라서 각 파드끼리 통신이 가능합니다.

# 클러스터에 있는 모든 네임스페이스를 나열
$ kubectl get ns

# 특정 네임스페이스의 파드 조회 (-n 으로 축약 가능)
$ kubectl get po --namespace <네임스페이스>

# 네임스페이스 생성 (yaml 파일로 만드는걸 추천)
$ kubectl create namespace <네임스페이스>

# 특정 네임스페이스에 파드 생성
$ kubectl apply -f kubia-manual.yaml -n <네임스페이스>

9. 레플리카셋 (ReplicaSet)

레플리카셋은 파드를 관리하는 역할을 합니다.

지정된 숫자보다 파드가 적으면 새로 생성하고, 더 많으면 삭제하는 등 항상 일정 갯수의 파드를 유지하도록 관리합니다.

파드에 문제가 생겨 갑자기 삭제되어도 레플리카셋이 부족한 파드를 새로 생성해주기 때문에 안정적으로 서비스를 운영할 수 있습니다.

과거에는 레플리케이션 컨트롤러를 사용했으나 이제 상위호환인 레플리카셋을 사용합니다.

# 레플리카셋 조회
$ kubectl get rs

10. 데몬셋 (DaemonSet)

데몬셋은 레플리카셋과 유사하지만 조금 다릅니다.

노드를 기준으로 파드 생성을 관리하며 노드 셀렉터를 사용해서 특정 노드들에만 파드를 생성시킬 수 있습니다.

# 데몬셋 조회
$ kubectl get ds

11. 잡 (Job)

잡은 지속적으로 실행되는 파드와 다르게 수행 후 종료되는 태스크입니다.

# 잡 조회
$ kubectl get jobs

+ Recent posts