DDD 9기 동아리 활동이 끝나고 (팀원들과는 출시까지 더 하기로함 !!) AWS 에 구축해두었던 jenkins
서버를 중지시키고 GitHub Actions
로 이동하기 위해 과정들을 정리하고자 한다. (AWS 너무 비싸)
👉 GitHub Organization 생성
먼저 DDD Organization 에서 탈주했다. 이후에 추가될 내용은 Sikdorok(서비스명 이하 식도록)
커뮤니티로 옮겨서 작업할 예정이였다. 그 이유는 Jira 를 연동해서 사용하고 싶었는데 동아리 활동 기간 중 운영진에게 요청하였으나 뭔가 제대로 연결이 이루어 지지 않았다. 아마도 운영진 계정이 직접 Jira 에 참여해야 하는 듯 하다. 그래서 신규로 조직을 구성하고 Jira 까지 연동 깔끔하게 성공 !!
👉 GitHub Actions yml 생성
GitHub Actions yml 을 생성하기 위해 신규로 워크플로우를 추가한다. 간단하게 Java with Gradle
Configure로 시작하기 위해 해당 설정값을 선택한 후 커밋 및 푸시한다.
👉 Actions secrets and variables 설정
파이프라인 내에서 사용할 보안처리가 필요한 설정값들을 저장하는 곳이다.
레포지토리의 Settings
에 들어가면 좌측 메뉴 중 Secrets and variables > Actions
가 있으며 New repository secret
을 클릭하고 원하는 값들을 설정하면 된다.
파이프라인에서 호출할 때는 다음처럼 호출한다.
${{ secrets.변수값 }}
👉 gradle.yml 파이프라인 작성
우선 전체 폼은 하단과 같다. 이후 Step 단위로 살펴보자.
name: Deploy
on:
push:
branches: [ "dev" ] # multiple - [ 'dev', 'prod' ]
# env
env:
SPRING_PROFILES_ACTIVE: ${{ github.ref == 'refs/heads/dev' && 'dev' || 'prod' }}
SLACK_CHANNEL: "#slack-deploy"
IMAGE_TAG: "latest"
ECR_REPOSITORY: "ecr-repository"
CONFIG_SERVER_ENCRYPT_KEY: ${{ secrets.CONFIG_SERVER_ENCRYPT_KEY }}
CONFIG_SERVER_USERNAME: ${{ secrets.CONFIG_SERVER_USERNAME }}
CONFIG_SERVER_PASSWORD: ${{ secrets.CONFIG_SERVER_PASSWORD }}
jobs:
build:
runs-on: ubuntu-latest
steps:
# Slack Notification
- name: Slack Notification - Actions Start
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
attachments: [{
color: 'good',
text: `==================================================================\n배포 파이프라인이 시작되었습니다.`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always() # Pick up events even if the job fails or is canceled.
# Checkout
- name: Checkout
uses: actions/checkout@v3
# JDK Setup
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Gradle Permission
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Gradle clean build test
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
env:
SPRING_PROFILES_ACTIVE: ${{ env.SPRING_PROFILES_ACTIVE }}
with:
arguments: :app-api:clean :app-api:build :app-api:test
# Slack Notification - Build Test Failure
- name: Slack Notification - Build Test Failure
uses: 8398a7/action-slack@v3
with:
status: failure
custom_payload: |
{
attachments: [{
color: 'danger',
text: `Build Test에 실패하였습니다.\n==================================================================`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure()
# Slack Notification - Build Test Success
- name: Slack Notification - Build Test Success
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
attachments: [{
color: 'good',
text: `Build Test에 성공하였습니다.`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: success()
# AWS 자격 인증 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
# ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# ECR 도커 이미지 Push
- name: Push docker image to ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
docker build --build-arg SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE --build-arg CONFIG_SERVER_ENCRYPT_KEY=$CONFIG_SERVER_ENCRYPT_KEY --build-arg CONFIG_SERVER_USERNAME=$CONFIG_SERVER_USERNAME --build-arg CONFIG_SERVER_PASSWORD=$CONFIG_SERVER_PASSWORD -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ./app-api
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
# Slack Notification - Container Image Build And Push Failure
- name: Slack Notification - Container Image Build And Push Failure
uses: 8398a7/action-slack@v3
with:
status: failure
custom_payload: |
{
attachments: [{
color: 'danger',
text: `Container Image Build And Push에 실패하였습니다.\n==================================================================`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure()
# Slack Notification - Container Image Build And Push Success
- name: Slack Notification - Container Image Build And Push Success
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
attachments: [{
color: 'good',
text: `Container Image Build And Push에 성공하였습니다.`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: success()
# GitHub Action IP
- name: Get Github Actions IP
id: ip
uses: haythem/public-ip@v1.2
# AWS API Server EC2 Inbound Authorize
- name: Add Github Actions IP to API Server Security group
run: |
aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
# SSH 연결
- name: SSH Remote Commands
uses: appleboy/ssh-action@v1.0.0
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_REGISTRY_URL_SIKDOROK: ${{ secrets.AWS_REGISTRY_URL_SIKDOROK }}
ECR_REPOSITORY: ${{ env.ECR_REPOSITORY }}
IMAGE_TAG: ${{ env.IMAGE_TAG }}
with:
host: ${{ env.SPRING_PROFILES_ACTIVE == 'prod' && '' || '3.37.134.103' }}
username: ubuntu
key: ${{ env.SPRING_PROFILES_ACTIVE == 'prod' && secrets.PROD_KEY || secrets.DEV_KEY }}
port: 22
envs: AWS_REGION, AWS_REGISTRY_URL_SIKDOROK, ECR_REPOSITORY, IMAGE_TAG
script: |
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_REGISTRY_URL_SIKDOROK
IMAGE_NAME=$ECR_REPOSITORY IMAGE_STORAGE=$AWS_REGISTRY_URL_SIKDOROK BUILD_NUMBER=$IMAGE_TAG ./deploy.sh
# Slack Notification - Server Run Failure
- name: Slack Notification - Server Run Failure
uses: 8398a7/action-slack@v3
with:
status: failure
custom_payload: |
{
attachments: [{
color: 'danger',
text: `배포에 실패하였습니다.\n==================================================================`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure()
# Slack Notification - Server Run Success
- name: Slack Notification - Server Run Success
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
attachments: [{
color: 'good',
text: `배포에 성공하였습니다.\n==================================================================\n`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: success()
# AWS API Server EC2 Inbound Revoke
- name: Remove Github Actions IP From API Server Security Group
if: always()
run: |
aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
# Slack Notification - Deploy Done
- name: Slack Notification - Deploy Done
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took,pullRequest # selectable (default: repo,message)
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
if: always()
엄청 길지만 Slack Notification 때문에 중간에 많이 들어가서 그렇지 총 4단계로 이루어져 있다.
👉 OS 선택
jobs:
build:
runs-on: ubuntu-latest
steps:
# Slack Notification
- name: Slack Notification - Actions Start
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
attachments: [{
color: 'good',
text: `==================================================================\n배포 파이프라인이 시작되었습니다.`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always() # Pick up events even if the job fails or is canceled.
해당 파이프라인은 서버와 동일하게 ubuntu
에서 시작한다. 그리고 배포 파이프라인 시작을 알리는 Slack 알림도 전송한다.
(여기서 Slack Webhook URL 을 생성하는 과정은 생략한다.)
👉 Build Test
# Checkout
- name: Checkout
uses: actions/checkout@v3
# JDK Setup
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Gradle Permission
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Gradle clean build test
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
env:
SPRING_PROFILES_ACTIVE: ${{ env.SPRING_PROFILES_ACTIVE }}
with:
arguments: :app-api:clean :app-api:build :app-api:test
# Slack Notification - Build Test Failure
- name: Slack Notification - Build Test Failure
uses: 8398a7/action-slack@v3
with:
status: failure
custom_payload: |
{
attachments: [{
color: 'danger',
text: `Build Test에 실패하였습니다.\n==================================================================`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure()
# Slack Notification - Build Test Success
- name: Slack Notification - Build Test Success
uses: 8398a7/action-slack@v3
with:
status: custom
custom_payload: |
{
attachments: [{
color: 'good',
text: `Build Test에 성공하였습니다.`,
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: success()
- Checkout
- JDK Setup
- Gradle Permission
- Gradle clean build test
- Slack Notification
위와 같은 순서대로 쭉 진행하여 빌드과정을 거친다. 이후 단계에서는 step 사이에 Slack Notification 단계는 설명을 생략한다.
👉 AWS 로그인 및 ECR Docker Image Push
# AWS 자격 인증 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRETS_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
# ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# ECR 도커 이미지 Push
- name: Push docker image to ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
docker build --build-arg SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE --build-arg CONFIG_SERVER_ENCRYPT_KEY=$CONFIG_SERVER_ENCRYPT_KEY --build-arg CONFIG_SERVER_USERNAME=$CONFIG_SERVER_USERNAME --build-arg CONFIG_SERVER_PASSWORD=$CONFIG_SERVER_PASSWORD -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ./app-api
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- AWS 자격 인증 설정
- ECR 로그인
- ECR 도커 이미지 Push
1번 과정에서 필요한 AWS_ACCESS_KEY
, AWS_SECRETS_ACCESS_KEY
는 AWS 의 IAM
에서 사용자를 생성하고 키를 발급받아 설정해야한다. ECR
서비스를 이용하기 위해선 사용자가 ECR
권한이 필요하다.
2번 과정에서 로그인을 처리한다. (실제로 로그인하는 과정의 내용은 하단의 첨부 글 중에서 확인 가능)
3번 과정에서 ECR_REGISTRY
값을 실제 ECR
URL을 사용하지 않는 이유는 2번 과정에서 로그인을 하고 나면 Token 값을 전달받는데 이는 임시로 사용가능한 상황이기 때문에 로그인 후 나온 Token 값을 통해서 하는 것 같다. (GitHub Actions 에서 저 Actions 은 어떤건지는 확인해보지 않았음. 궁금하면 직접 Actions 파헤쳐보길)
이후 docker build
를 하고 docker push
를 한다.
위에서는
Cloud Config
를 사용하기 때문에 추가된 옵션이 있으나 필요하지 않은 경우 삭제할 것!
그리고build
과정에서Dockerfile
위치를 찾기 때문에./app-api
가 아닌.
으로 작성해야한다. 글쓴이는 멀티모듈이기 때문에 해당 경로를 직접 지정해주었다.
👉 GitHub Ip > Inbound Ingress
# GitHub Action IP
- name: Get Github Actions IP
id: ip
uses: haythem/public-ip@v1.2
# AWS API Server EC2 Inbound Authorize
- name: Add Github Actions IP to API Server Security group
run: |
aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
- GitHub Action IP 를 획득
- 획득한 IP로 배포하려는 EC2 Server 의 Inbound 에 IP 추가
👉 SSH 연결을 통해 배포
# SSH 연결
- name: SSH Remote Commands
uses: appleboy/ssh-action@v1.0.0
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_REGISTRY_URL_SIKDOROK: ${{ secrets.AWS_REGISTRY_URL_SIKDOROK }}
ECR_REPOSITORY: ${{ env.ECR_REPOSITORY }}
IMAGE_TAG: ${{ env.IMAGE_TAG }}
with:
host: ${{ env.SPRING_PROFILES_ACTIVE == 'prod' && '배포 서버 IP' || '배포 서버 IP' }}
username: ubuntu
key: ${{ env.SPRING_PROFILES_ACTIVE == 'prod' && secrets.PROD_KEY || secrets.DEV_KEY }}
port: 22
envs: AWS_REGION, AWS_REGISTRY_URL_SIKDOROK, ECR_REPOSITORY, IMAGE_TAG
script: |
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_REGISTRY_URL_SIKDOROK
IMAGE_NAME=$ECR_REPOSITORY IMAGE_STORAGE=$AWS_REGISTRY_URL_SIKDOROK BUILD_NUMBER=$IMAGE_TAG ./deploy.sh
- 배포하는 서버에서 ECR 로그인을 한다. (이전 jenkins 구축 당시 한번해두어도 자꾸 풀려서 배포 단계에서 항상 로그인되게 처리함)
- 배포 스크립트를 작성해둔 것을 실행
참고로 script 는 2줄이다 ! 1줄이 아님
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_REGISTRY_URL_SIKDOROK
IMAGE_NAME=$ECR_REPOSITORY IMAGE_STORAGE=$AWS_REGISTRY_URL_SIKDOROK BUILD_NUMBER=$IMAGE_TAG ./deploy.sh
위와 같은 과정의 일부분을 이해하고 싶다면 첨부한 글들을 잠시 보고오면 좋을 것 같다.
👉 GitHub Ip > Inbound Revoke
# AWS API Server EC2 Inbound Revoke
- name: Remove Github Actions IP From API Server Security Group
if: always()
run: |
aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
배포 과정이 끝났으니 성공하든 실패하든 잠시 넣어두었던 Ip 를 제거한다.
🚨 에러대응
- Inbound 에 넣으려고 해당 명령줄을 실행했더니 인코딩된 Exception Message 를 받아서 해당 값을 디코딩하기 위해선 다음 정책의 권한이 필요했다.
aws sts decode-authorization-message --encoded-message {인코딩 메시지}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowStsDecode",
"Effect": "Allow",
"Action": "sts:DecodeAuthorizationMessage",
"Resource": "*"
}
]
}
- 1번에서 디코딩한 메시지를 확인해보니 GitHub Ip 를 Inbound 에 추가, 수정 및 삭제를 하기 위해서 다음 정책의 권한이 필요했다.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress" ], "Resource": "*" } ] }
'DevOps > CI CD' 카테고리의 다른 글
AWS ECR + Jenkins CI CD 구축하기 (0) | 2023.07.14 |
---|---|
Bitbucket SSH key Change (0) | 2023.06.21 |
Docker + Nginx + Jenkins CI / CD 무중단 배포 (0) | 2023.03.20 |