IT STUDY LOG

[SECTION2] <AWS 배포 자동화> DAY 2 LOG 본문

devops bootcamp 4/project log

[SECTION2] <AWS 배포 자동화> DAY 2 LOG

roheerumi 2023. 4. 28. 23:59

#학습 목표

  • 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 적용

 

#과제 항목별 진행 상황

✏️  마일스톤4 - 이미지 ECS 배포

Step 1 : 연습과제: 서버 이미지 ECS 배포 (참고 Amazon ECS 구성요소 공식 문서)

1. ECS 콘솔에 접속해 태스크 정의작성

1-1. 태스크 정의 및 컨테이너 구성

  1. 태스크 정의 이름 설정
  2. 컨테이너명과 이미지 URI (ECR 레파지토리 주소/사용할 이미지:태그) 작성
  3. 컨테이너 내부 포트 지정
  4. 설정할 환경변수 설정

1-2. 환경, 스토리지, 모니터링 및 태그 구성

1-3. 검토 및 생성

2. ECS 콘솔에서 클러스터 생성

  1. [클러스터 구성]에서 클러스터명 작성
  2. [네트워킹]에서 VPC를 선택하고 서브넷을 선택할 때 필요하지 않은 서브넷은 제거하고 생성할 것

3. ECS 콘솔에서 클러스터에서 서비스 생성 후 태스크 실행

3-1. 클러스터 > 생성한 클러스터 선택 > 서비스 생성

  1. [환경]에서 컴퓨팅 옵션 선택
  2. [배포 구성]에서 애플리케이션 유형 지정
  3. [배포 구성]의 패밀리에 이전에 지정한 작업 정의와 개정 버전을 선택
  4. [배포 구성]에서 서비스명 작성
  5. [배포 구성]에서 실행할 태스크 수 작성
  6. [네트워킹]에서 VPC, 서브넷, 보안 그룹 선택
  7. [로드밸런싱]에서 해당 서비스에 적합하게 사용할 로드밸런서 유형을 선택
    • 웹 애플리케이션 서버의 경우 애플리케이션 레벨의 ALB
    • DB 서버의 경우 TCP 통신을 하므로 NLB 사용
  8. [로드밸런싱]에서 기존/새로운 로드밸런서를 사용할지 선택하고 이름 설정
  9. [로드밸런싱]에서 로드밸런싱할 컨테이너(작업 정의에서 생성한)를 선택
  10. [로드밸런싱]에서 로드밸런서에서 사용할 리스너 포트 추가
  11. [로드밸런싱]에서 대상그룹을 생성하거나 기존 대상 그룹 사용
  12. 생성

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 생성

  1. [기본 구성]에서 로드밸런서 이름 설정
  2. [네트워크 매핑]에서 VPC를 선택하고 2개 이상의 가용 영역을 선택하고 각 AZ마다 서브넷을 하나씩 선택
  3. [리스너 및 라우팅]에서 생성할 리스너의 프로토콜과 포트(이 경우 80)를 설정
  4. [리스너 및 라우팅]의 기본 작업으로 대상 그룹을 선택하기 위해 대상 그룹 생성하고 지정
    • 대상 그룹 생성의 1단계 : 그룹 세부 정보 지정의 [기본 구성]으로 대상 유형을 지정 (이 경우 IP 주소)
      • instance: 대상이 인스턴스 ID에 의해 지정
      • ip: 대상이 IP 주소
      • lambda: 대상이 Lambda 함수
    • [기본 구성]에서 대상 그룹 이름 설정
    • [기본 구성]에서 로드밸런서의 리스너로 받은 패킷을 전달할 서버 프로토콜(이 경우 27017)을 설정
    • 대상 그룹 생성의 2단계 : 대상 등록의 [IP 주소], [대상 보기] 설정 후 대상 그룹 생성
  5. 네트워크 로드밸런서 생성

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. 배포하기 전 로컬에서 테스트

  1. 로컬 테스트 방법
    • 필요한 환경 변수를 export 명령을 이용해 주입
    • npm start 명령을 이용해 실행 시 DB 접속
  2. 도커 이미지 테스트 방법
    • 도커 파일을 만들고 난 후, --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단계 보안 암호 유형 선택에서 [보안 암호 유형]에서 다른 유형의 보안 암호 선택
  2. 1단계 보안 암호 유형 선택에서 [키/값 페어]에서 다음의 키/값들을 입력
    • MONGO_HOSTNAME: mongodb의 엔드포인트 (ECR의 DB이미지로 만든 태스크의 NLB의 DNS 주소)
    • MONGO_USERNAME: mongodb의 사용자 이름
    • MONGO_PASSWORD: mongodb의 비밀번호
  3. 2단계 보안 암호 구성에서 [보안 암호 이름]을 잘 구분할 수 있는 이름으로 지정
  4. 3단계 교체 구성에서 [자동 교체]는 미적용 후 검토하여 새 보안 암호 저장

3. 새로운 작업 정의 생성

- Step 1의 태스크 정의와 동일하게 태스크 정의를 새롭게 개정하되, 환경 변수를 설정

  1. 키: 실제로 애플리케이션에서 사용할 환경 변수 이름
  2. 값 유형: ValueFrom으로 설정
  3. 값: 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을 이용해 해당 도메인 주소에 인증서 발급

 

퍼블릭 인증서 요청 - AWS Certificate Manager

목록을 정렬한 방법에 따라 찾고 있는 인증서가 즉시 표시되지 않을 수 있습니다. 오른쪽의 검은색 삼각형을 클릭하여 순서를 변경할 수 있습니다. 오른쪽 상단의 페이지 번호를 사용하여 여러

docs.aws.amazon.com

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

 

GitHub - aws-actions/amazon-ecs-deploy-task-definition: Registers an Amazon ECS task definition and deploys it to an ECS service

Registers an Amazon ECS task definition and deploys it to an ECS service. - GitHub - aws-actions/amazon-ecs-deploy-task-definition: Registers an Amazon ECS task definition and deploys it to an ECS ...

github.com

 

📝 문제 3 : 컨테이너로 WAS와 DB를 따로 기동 후 WAS에서 DB 접근 불가

  • 원인
    1. compose로 함께 기동하지 않고 따로 기동할 경우 브릿지가 생성되지 않음
    2. 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",
# ... 하략 ....#

                }
            }
        }
    }
]
  • 해결 방법
    1. mongo DB의 config를 변경해 외부 네트워크에서 접근 가능하도록 설정
    2. (채택) 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

https://docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/application/introduction.html#application-load-balancer-components

 

Application Load Balancer란 무엇입니까? - Elastic Load Balancing

Application Load Balancer란 무엇입니까? Elastic Load Balancing은 둘 이상의 가용 영역에서 EC2 인스턴스, 컨테이너, IP 주소 등 여러 대상에 걸쳐 수신되는 트래픽을 자동으로 분산 등록된 대상의 상

docs.aws.amazon.com

Using Secrets Manager to secure sensitive data

 

Passing sensitive data to a container - Amazon Elastic Container Service

Thanks for letting us know this page needs work. We're sorry we let you down. If you've got a moment, please tell us how we can make the documentation better.

docs.aws.amazon.com

 

Comments