IT STUDY LOG
[SECTION2] <AWS 배포 자동화> DAY 2 LOG 본문
#학습 목표
- WAS 및 mongoDB 이미지를 ECS를 통해 배포
- WAS 이미지의 배포 자동화를 구현
- WAS와 연결된 로드 밸런서에 HTTPS를 적용
- 로드 밸런서와 ECS 서비스를 연결
- GitHub Action을 이용해 ECS 배포 과정 workflow를 추가
- 민감 정보가 절대 Git에 노출되지 않도록 설정
#해결 과제
💡 마일스톤4 - 이미지 ECS 배포
- ECS의 클러스터, 태스크 정의, 태스크, 서비스에 대한 개념을 이해
- ECR에 저장된 웹 서버 이미지를 ECS로 배포
- ECS 태스크의 로그 확인
- mongodb 이미지를 ECS를 이용해 배포
- mongodb 클라이언트를 이용해, 컨테이너에 접속이 가능한지 확인
- 애플리케이션 로드 밸런서와 네트워크 로드 밸런서의 차이에 대해 이해
💡 마일스톤5 - 서버 이미지 ECS 배포 자동화
- 웹 서버 이미지의 새 버전이 준비되었을 때, 새로운 작업 정의와 서비스를 만들고, 새 버전이 배포되도록 설정
- 고의적으로 애플리케이션이 정상적으로 실행되지 못하게 만들어서, 배포가 실패하는 것을 로그를 통해 확인
- WAS에서 mongodb에 접속하고, 쿼리 해보면서 접속이 정상적인지 확인
- 데이터베이스 연결 등에 필요한 민감정보를 어떻게 컨테이너에 주입시키는지 확인
- GitHub Action을 이용해 ECS 배포 과정 workflow를 추가
- 민감 정보가 절대 Git에 노출되지 않도록 설저
💡 마일스톤6 - 서버 애플리케이션의 HTTPS 적용
- https://본인도메인.click의 트래픽이 서버 컨테이너(즉 애플리케이션 로드 밸런서)로 연결되도록 설정
#과제 항목별 진행 상황
✏️ 마일스톤4 - 이미지 ECS 배포
Step 1 : 연습과제: 서버 이미지 ECS 배포 (참고 Amazon ECS 구성요소 공식 문서)
1. ECS 콘솔에 접속해 태스크 정의작성
1-1. 태스크 정의 및 컨테이너 구성
- 태스크 정의 이름 설정
- 컨테이너명과 이미지 URI (ECR 레파지토리 주소/사용할 이미지:태그) 작성
- 컨테이너 내부 포트 지정
- 설정할 환경변수 설정
1-2. 환경, 스토리지, 모니터링 및 태그 구성
1-3. 검토 및 생성
2. ECS 콘솔에서 클러스터 생성
- [클러스터 구성]에서 클러스터명 작성
- [네트워킹]에서 VPC를 선택하고 서브넷을 선택할 때 필요하지 않은 서브넷은 제거하고 생성할 것
3. ECS 콘솔에서 클러스터에서 서비스 생성 후 태스크 실행
3-1. 클러스터 > 생성한 클러스터 선택 > 서비스 생성
- [환경]에서 컴퓨팅 옵션 선택
- [배포 구성]에서 애플리케이션 유형 지정
- [배포 구성]의 패밀리에 이전에 지정한 작업 정의와 개정 버전을 선택
- [배포 구성]에서 서비스명 작성
- [배포 구성]에서 실행할 태스크 수 작성
- [네트워킹]에서 VPC, 서브넷, 보안 그룹 선택
- [로드밸런싱]에서 해당 서비스에 적합하게 사용할 로드밸런서 유형을 선택
- 웹 애플리케이션 서버의 경우 애플리케이션 레벨의 ALB
- DB 서버의 경우 TCP 통신을 하므로 NLB 사용
- [로드밸런싱]에서 기존/새로운 로드밸런서를 사용할지 선택하고 이름 설정
- [로드밸런싱]에서 로드밸런싱할 컨테이너(작업 정의에서 생성한)를 선택
- [로드밸런싱]에서 로드밸런서에서 사용할 리스너 포트 추가
- [로드밸런싱]에서 대상그룹을 생성하거나 기존 대상 그룹 사용
- 생성
4. 생성된 태스크들의 퍼블릭 IP 주소의 3000포트, ALB의 DNS 주소로 접근했을 때 정상적으로 웹서버가 동작하는지 확인
4-1. [로그] 탭에서 서비스가 정상적으로 기동되는지 확인 가능
4-2. [구성 및 태스크] 탭에서 태스크 상세 내역 확인 가능
4-3. 특정 태스크를 선택해 태스크 상세 내역 확인
4-4. 태스크 퍼블릭 IP:3000, ALB DNS 주소로 접속했을 시 정상 기동 확인
Step 2 : 실전과제: 서버 컨테이너화와 레지스트리로 push
1. EC2 콘솔에서 Network Load Balancer 생성
- [기본 구성]에서 로드밸런서 이름 설정
- [네트워크 매핑]에서 VPC를 선택하고 2개 이상의 가용 영역을 선택하고 각 AZ마다 서브넷을 하나씩 선택
- [리스너 및 라우팅]에서 생성할 리스너의 프로토콜과 포트(이 경우 80)를 설정
- [리스너 및 라우팅]의 기본 작업으로 대상 그룹을 선택하기 위해 대상 그룹 생성하고 지정
- 대상 그룹 생성의 1단계 : 그룹 세부 정보 지정의 [기본 구성]으로 대상 유형을 지정 (이 경우 IP 주소)
- instance: 대상이 인스턴스 ID에 의해 지정
- ip: 대상이 IP 주소
- lambda: 대상이 Lambda 함수
- [기본 구성]에서 대상 그룹 이름 설정
- [기본 구성]에서 로드밸런서의 리스너로 받은 패킷을 전달할 서버 프로토콜(이 경우 27017)을 설정
- 대상 그룹 생성의 2단계 : 대상 등록의 [IP 주소], [대상 보기] 설정 후 대상 그룹 생성
- 대상 그룹 생성의 1단계 : 그룹 세부 정보 지정의 [기본 구성]으로 대상 유형을 지정 (이 경우 IP 주소)
- 네트워크 로드밸런서 생성
2. 터미널에서 mongodb 이미지를 빌드해 ECR로 푸시
$ aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com
$ docker tag 9a5e0d0cf6de 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/<ECR_REPOSITORY_NAME>:mongodb
$ docker push 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/<ECR_REPOSITORY_NAME>:mongodb
3. Step 1과 동일하게 태스크 정의 > 클러스터 생성 > 서비스 생성 과정을 진행하며 로드밸런서로 NLB를 선택해 서비스와 태스크 상세 내역 확인
4. 태스크의 퍼블릭 IP와 DNS 주소로 접근하여 서버가 정상적으로 동작하는지 확인
✏️ 마일스톤5 - 서버 이미지 ECS 배포 자동화
Step 1 : 연습과제: WAS에서 mongodb로의 접속
1. Fastify용 mongodb 플러그인 설치
$ npm i @fastify/mongodb
2. mongodb 플러그인 적용 - plugins/mongodb.js 파일 생성
'use strict'
const fp = require('fastify-plugin')
const { MONGO_HOSTNAME, MONGO_USERNAME, MONGO_PASSWORD } = process.env
module.exports = fp(async function (fastify, opts) {
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:27017/?authMechanism=DEFAULT`
console.log(url)
fastify.register(require('@fastify/mongodb'), {
forceClose: true,
url: url
})
})
3. 배포하기 전 로컬에서 테스트
- 로컬 테스트 방법
- 필요한 환경 변수를 export 명령을 이용해 주입
- npm start 명령을 이용해 실행 시 DB 접속
- 도커 이미지 테스트 방법
- 도커 파일을 만들고 난 후, --env 옵션을 이용해서 환경변수를 주입 (docker run --env MONGO_PASSWORD=secret)
4. (Optional) mongodb에 데이터 추가/조회 등을 테스트 - routes/example/index.js 생성
module.exports = async function (fastify, opts) {
fastify.post('/', async function (request, reply) {
const test = this.mongo.client.db('test')
const article = test.collection('article')
await article.insertOne({ 'title': '안녕 세상아' })
const result = await article.find({}).toArray();
reply.send(result)
})
}
Step 3 : 연습과제: ECS에서 민감 정보 다루기
1. Fastify로 작성된 WAS의 routes/root.js 파일 수정 후 git repository에 add, commit, push
'use strict'
module.exports = async function (fastify, opts) {
fastify.get('/', async function (request, reply) {
console.log(process.env.MONGO_HOSTNAME)
console.log(process.env.MONGO_USERNAME)
return { root: true, host: process.env.MONGO_HOSTNAME, username: process.env.MONGO_USERNAME }
})
}
2. 민감 정보 입력
2-1. 콘솔에서 AWS Secrets Manager로 이동 후 새 보안 암호 저장 선택
2-2. 보안 암호 상세 내용 설정 후 저장
- 1단계 보안 암호 유형 선택에서 [보안 암호 유형]에서 다른 유형의 보안 암호 선택
- 1단계 보안 암호 유형 선택에서 [키/값 페어]에서 다음의 키/값들을 입력
- MONGO_HOSTNAME: mongodb의 엔드포인트 (ECR의 DB이미지로 만든 태스크의 NLB의 DNS 주소)
- MONGO_USERNAME: mongodb의 사용자 이름
- MONGO_PASSWORD: mongodb의 비밀번호
- 2단계 보안 암호 구성에서 [보안 암호 이름]을 잘 구분할 수 있는 이름으로 지정
- 3단계 교체 구성에서 [자동 교체]는 미적용 후 검토하여 새 보안 암호 저장
3. 새로운 작업 정의 생성
- Step 1의 태스크 정의와 동일하게 태스크 정의를 새롭게 개정하되, 환경 변수를 설정
- 키: 실제로 애플리케이션에서 사용할 환경 변수 이름
- 값 유형: ValueFrom으로 설정
- 값: Secrets Manager에 저장되어 있는 보안 암호의 키/값의 ARN으로 지정 (형식: arn:aws:secretsmanager:region:aws_account_id:secret:appauthexample-AbCdEf:username1::)
- appauthexample: 보안 암호 이름
- AbCdEf: 일종의 해시값으로 ARN에서 확인 가능
- username1: 보안 암호의 키(현 실습에서 사용하는 MONGO_USERNAME 등으로 지정)
4. 작업 정의의 최신 개정을 바탕으로 서비스 업데이트
4-1. 기존에 만들어두었던 서비스를 선택하고 [서비스 업데이트] 선택
4-2. 작업 정의의 최신 개정을 선택해서 서비스를 업데이트하면, 태스크가 새로운 개정으로 업데이트되며, 이전 개정 버전은 비활성화되고 중단되어 서비스 생성 시 지정한 태스크 수만큼 최신 개정 태스크가 동작
5. 태스크에 접속 후, 환경변수 적용 확인 : 로드 밸런서나, 각 태스크의 퍼블릭 IP를 통해 WAS에 접근
Step 4 : 실습과제: 작업 정의 파일을 이용한 ECR로 배포 자동화
- 참고 : 데이터베이스 이미지 자체가 변경되는 일이 적으므로, 처음 한 번만 배포하면 되기 때문에 mongodb의 배포 자동화를 할 필요 없음
- ⚠️ 작업 정의 파일에 민감 정보가 포함되어 있는지 여부를 반드시 확인
1. 작업 정의 JSON 파일을 Git repository에 추가
1-1. ECS 콘솔에서 태스크 정의 > 자동화 할 태스크 개정 버전 선택 > JSON > JSON 다운로드
1-2. JSON 형식의 작업 정의를 Git Repository에 적당한 곳에 위치
2. ECS로의 배포를 위한 GitHub Action workflow를 추가
2-1. GitHub Action에 Deploy to Amazon ECS라는 이름의 workflow를 추가 여러 가지 환경 설정하여 배포 자동화
2-2. workflow 파일 내용
name: Deploy to AMAZON ECS
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
AWS_REGION: "ap-northeast-2" # set this to your preferred AWS region, e.g. us-west-1
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} # set this to your Amazon ECR repository name
ECS_SERVICE: helloworld-was-svc # set this to your Amazon ECS service name
ECS_CLUSTER: helloworld-was-cluster # set this to your Amazon ECS cluster name
ECS_TASK_DEFINITION: helloworld-was-task-definitions.json # set this to the path to your Amazon ECS task definition
# file, e.g. .aws/task-definition.json
CONTAINER_NAME: helloworld-was-container # set this to the name of the container in the
# containerDefinitions section of your task definition
ROLE_ARN: ${{ secrets.ECR_ROLE_ARN }}
permissions:
contents: read
id-token: write # This is required for requesting the JWT
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: "1. github runner에 레파지토리 체크아웃"
uses: actions/checkout@v3
- name: "2. AWS credential 설정"
uses: aws-actions/configure-aws-credentials@v2
with:
aws-region: ${{ env.AWS_REGION }}
role-session-name: GitHubActions
role-to-assume: ${{ env.ROLE_ARN }}
# Login to Amazon ECR Private, then build and push a Docker image: (cf. public)
- name: "3. AMAZON ECR 로그인"
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: "4. AMAZON ECR에 이미지 빌드, 태그, 푸시"
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
cd ./helloworld-was
docker build -t $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG .
docker push $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG
echo "image=$ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG" >> $GITHUB_OUTPUT
cd ../
- name: "5. AMAZON ECS 작업 정의 안에 새로운 이미지ID 입력"
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.ECS_TASK_DEFINITION }}
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- name: "6. 아마존 ECS "
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
2-3. 기존의 ECR로의 빌드/push 작업 자동화 workflow가 있는 경우에 해당 내용이 중복이므로 기존 workflow 삭제
✏️ 마일스톤6 - 서버 애플리케이션의 HTTPS 적용
Step 1 : 실습과제: 서버 애플리케이션의 HTTPS 적용
1. Route53를 이용해 구입한 도메인에 ALB 연결
1-1. Route53 콘솔 > 호스팅 영역 > 레코드 생성
1-2. HTTPS 적용이 가능한 인증서가 없을 경우 ACM을 이용해 해당 도메인 주소에 인증서 발급
1-3. 도메인으로 접속 시 SSL 적용되어 정상적으로 웹 애플리케이션 서버로 접속되는 것 확인
#TROUBLE SHOOTING LOG
📝 문제 1 : mongodb의 태스크 정의 후 태스크 작동 실패
- 원인 : 이미지 URI에 잘못된 URI 입력
- 해결 방안 : 정상적인 ECR 레파지토리와 태그 입력
📝 문제 2 : 빌드 후 ECR에 이미지가 배포되었을 때 자동으로 서비스에 배포하는 workflow 실행 시 ECR 배포 실패
- 원인 : 작업 정의 접근 권한 문제로 배포 자동화 실패
- 해결 방안 : IAM의 역할에 필요한 권한 부여
{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"RegisterTaskDefinition",
"Effect":"Allow",
"Action":[
"ecs:RegisterTaskDefinition"
],
"Resource":"*"
},
{
"Sid":"PassRolesInTaskDefinition",
"Effect":"Allow",
"Action":[
"iam:PassRole"
],
"Resource":[
"arn:aws:iam::<aws_account_id>:role/<task_definition_task_role_name>",
"arn:aws:iam::<aws_account_id>:role/<task_definition_task_execution_role_name>"
]
},
{
"Sid":"DeployService",
"Effect":"Allow",
"Action":[
"ecs:UpdateService",
"ecs:DescribeServices"
],
"Resource":[
"arn:aws:ecs:<region>:<aws_account_id>:service/<cluster_name>/<service_name>"
]
}
]
}
https://github.com/aws-actions/amazon-ecs-deploy-task-definition
📝 문제 3 : 컨테이너로 WAS와 DB를 따로 기동 후 WAS에서 DB 접근 불가
- 원인
- compose로 함께 기동하지 않고 따로 기동할 경우 브릿지가 생성되지 않음
- container는 독립된 네트워크로 동작하므로 두 서버가 동일한 서브넷에 속해있지 않으므로 통신이 불가능
$ docker run --name servertest -p 3000:3000 --env MONGO_PASSWORD=secret --env MONGO_HOSTNAME=172.22.0.2 --env MONGO_USERNAME=root --network devops-04-s2-team9_default ohrory218/helloworld:1.1
> helloworld-was@1.0.0 start
> fastify start -l info app.js --address 0.0.0.0
[INFO] app.js file processing
[INFO] app.js file done
[DB CONNECTION STRING] mongodb://root:secret@172.22.0.2:27017/?authMechanism=DEFAULT
MongoServerSelectionError: connect ECONNREFUSED 172.22.0.2:27017
at Timeout._onTimeout (/app/node_modules/@fastify/mongodb/node_modules/mongodb/lib/sdam/topology.js:277:38)
at listOnTimeout (node:internal/timers:569:17)
at process.processTimers (node:internal/timers:512:7) {
reason: TopologyDescription {
type: 'Unknown',
servers: Map(1) { '172.22.0.2:27017' => [ServerDescription] },
stale: false,
compatible: true,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
setName: null,
maxElectionId: null,
maxSetVersion: null,
commonWireVersion: 0,
logicalSessionTimeoutMinutes: null
},
code: undefined,
[Symbol(errorLabels)]: Set(0) {}
}
- 트러블 슈팅 과정
# 기동된 mongodb 컨테이너 네트워크 확인하여 환경 변수의 HOSTNAME에 IPAddress 기입, network에 networks명 기입
$ docker container inspect mongodb
[
{
#... 중략 ...#
"Networks": {
"devops-04-s2-team9_default": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"mongodb",
"mongo",
"223966663fe2"
],
# ... 중략 ....#
"IPAddress": "172.22.0.2",
# ... 하략 ....#
}
}
}
}
]
- 해결 방법
- mongo DB의 config를 변경해 외부 네트워크에서 접근 가능하도록 설정
- (채택) WAS 컨테이너를 실행할 때 DB 컨테이너가 실행된 네트워크에서 실행되도록 수정
$ docker run --name servertest -p 3000:3000 --env MONGO_PASSWORD=secret --env MONGO_HOSTNAME=172.22.0.2 --env MONGO_USERNAME=root --network devops-04-s2-team9_default ohrory218/helloworld:1.1
> helloworld-was@1.0.0 start
> fastify start -l info app.js --address 0.0.0.0
[INFO] app.js file processing
[INFO] app.js file done
[DB CONNECTION STRING] mongodb://root:secret@172.22.0.2:27017/?authMechanism=DEFAULT
{"level":30,"time":1682772429521,"pid":18,"hostname":"87140ace8e46","msg":"Server listening at http://0.0.0.0:3000"}
# References
Using Secrets Manager to secure sensitive data
'devops bootcamp 4 > project log' 카테고리의 다른 글
[SECTION2] <AWS 배포 자동화> DAY 4 LOG (0) | 2023.05.02 |
---|---|
[SECTION2] <AWS 배포 자동화> DAY 3 LOG (0) | 2023.05.01 |
[SECTION2] <AWS 배포 자동화> DAY 1 LOG (0) | 2023.04.27 |
[SECTION1] <WAS, Web Server 실습> 회고 (0) | 2023.04.05 |
[SECTION 1] <WAS, Web Server 실습> Introduction (0) | 2023.04.03 |