# Developer for ECS
이 세션에서는 AWS에서 컨테이너화된 애플리케이션을 구축, 배포 및 운영하는 프로세스를 간소화하는 명령줄 인터페이스(CLI) 도구인 AWS Copilot에 대해 논의합니다. 출시 시간 단축, 복잡성 감소, 일관성 개선 등 AWS Copilot 사용의 주요 이점을 다룰 것입니다.
다음으로 개발자가 AWS Copilot을 사용하여 컨테이너화된 애플리케이션을 만들고 Amazon Elastic Container Service(ECS) 또는 AWS Fargate에 배포하는 방법을 살펴보겠습니다. AWS Copilot을 설치 및 구성하는 단계를 살펴보고 이를 사용하여 애플리케이션의 인프라를 정의 및 배포하는 방법을 시연합니다.
또한 AWS Copilot을 사용하여 환경 생성 및 관리, 애플리케이션 모니터링 및 문제 해결, 배포 업데이트 및 롤백을 포함하여 애플리케이션의 전체 수명 주기를 관리하는 방법도 다룹니다.
세션 전반에 걸쳐 AWS Copilot을 사용하여 AWS에서 컨테이너화된 애플리케이션을 개발, 배포 및 운영하기 위한 실용적인 예와 모범 사례를 제공합니다. 이 세션이 끝나면 개발자가 AWS Copilot을 사용하여 애플리케이션 개발 및 배포 워크플로를 가속화하는 방법과 이 도구를 활용하여 AWS에서 애플리케이션의 안정성과 확장성을 개선하는 방법을 확실하게 이해할 수 있습니다.
---
# Docker 기본 사용
스프링 프로젝트를 docker container image로 변경하고 로컬에서 실행하는 방법을 알아봅니다.
## Sample project
```bash=
git clone https://github.com/sykang808/gs-spring-example.git
```
## Local Build and test
프로젝트를 클론하여 로컬에서 java build 후 테스트를 해봅니다.
```bash=
./gradlew bootJar
sudo java -jar -Dserver.port=8080 build/libs/ecs-spring-sykang.jar
```
localhost 접근하면 "hello world"를 만날 수 있습니다.
## Local Docker build
해당 빌드 파일을 도커 이미지로 만들기 위해 Dockerfile을 작성합니다. COPY 명령어로 필요한 파일을 복사하고 빌드산출물을 실행합니다.
### Make Dockerfile
```dockerfile=
FROM openjdk:8-jdk-alpine AS builder
COPY gradlew ./gradlew
COPY gradle ./gradle
COPY build.gradle ./build.gradle
COPY settings.gradle ./settings.gradle
COPY src ./src
RUN chmod +x ./gradlew
RUN ./gradlew bootJar
FROM builder
COPY --from=builder ./build/libs/*.jar app.jar
EXPOSE 80
ENTRYPOINT ["java","-jar","/app.jar"]
```
도커 이미지를 만들어봅니다.
```bash=
docker build -t test .
```
이미지가 잘 생성되었는지 확인합니다. test라는 이미지 레포지토리가 보이면 성공한 것입니다.
```bash=
docker images
```
도커에서 컨테이너를 실행합니다.
```bash=
docker run -p 8080:80 --name test test
```
localhost:80 접근하면 "hello world"를 만날 수 있습니다.
docker의 세부적인 변경사항을 추적해보겠습니다.
```bash=
docker ps -l
```
나온 Container id를 복사한다음 아래 명령어를 입력합니다.
```bash=
docker diff "container id"
```
변경사항을 확인할 수 있습니다.
아래명령으로 컨테이너에 직접 접속해보겠습니다.
```bash=
docker exec -it "container id" /bin/sh
```
ps를 입력하여 java 어플리케이션이 별도의 프로세스로 실행되는 것을 확인해봅니다.
### Public docker repository pull limit 해결
베이스 docker image는 openjdk:8-jdk-alpine입니다.
CodeBuild는 캐시되지 않으면 새로운 이미지를 Limit를 피하기위해 베이스 이미지를 private ECR로 복사하여 사용하겠습니다.
ECR로 이동하여 private에 openjdk레포지토리를 생성합니다.
ECR push명령에 따라 로그인을 하고 아래 태그를 붙여서 push합니다.
```bash=
docker tag openjdk:8-jdk-alpine ---.dkr.ecr.us-west-2.amazonaws.com/openjdk:8-jdk-alpine
docker push ---.dkr.ecr.us-west-2.amazonaws.com/openjdk:8-jdk-alpine
```
사용하는 도커 파일의 베이스 이미지를 변경합니다.
```dockerfile=
FROM ---.dkr.ecr.us-west-2.amazonaws.com/openjdk:8-jdk-alpine AS builder
```
이제 private ECR의 base이미지를 사용하기때문에 Limit를 걱정하지 않아도됩니다.
---
# ECS with Copilot
---
## 컨테이너 이미지 배포 - ECS 사용
ECS는 쉽고 간편합니다. 배포와 구성을 빠르게 하기위해 Copilot을 사용하겠습니다. Copilot은 3가지 메인컨셉이 있습니다.
- **Application** : 응용 프로그램은 시스템 조각에 대한 그룹화 메커니즘입니다. Conway의 법칙에 따라 구성 요소를 조직의 다른 팀에 해당하는 Copilot 응용 프로그램으로 분할합니다. 예를 들어, 모든 작업의 일부를 처리하는 통합 개발 팀이 있는 소규모 조직이 있는 경우 하나 이상의 서비스로 구성된 단일 애플리케이션으로 구성할 수 있습니다. 그러나 여러 팀이 있고 각각 단일 구성 요소 그룹을 담당하고 팀 간 작업이 거의 없는 경우 각 팀에는 고유한 Copilot 응용 프로그램이 있어야 합니다.
- **Environment** : 환경은 애플리케이션 배포의 한 단계입니다. 예를 들어 고객에게 영향을 주지 않고 테스트할 수 있도록 먼저 "QA" 환경에 애플리케이션을 배포할 수 있습니다. 의도한 대로 작동하는 것으로 확인되면 해당 버전의 애플리케이션을 "프로덕션" 환경에 배포하여 고객이 액세스할 수 있도록 합니다.
- **Service** : 서비스는 컨테이너 내에서 오래 실행되는 단일 코드 프로세스입니다. 애플리케이션은 하나 이상의 서비스로 구성됩니다. 모놀리식 아키텍처를 사용하는 경우 각 애플리케이션에 단일 서비스만 있을 가능성이 높습니다. 보다 분산된 아키텍처는 각 애플리케이션에 대해 여러 서비스를 활용합니다. 예를 들어 인터넷에 연결된 로드 밸런서가 있는 "웹사이트" 서비스, 서비스 검색을 통해서만 액세스할 수 있는 내부 "API" 서비스, 대기열 외부의 작업에서 작동하는 "백그라운드 작업자" 서비스가 있을 수 있습니다. 이러한 서비스는 함께 단일 응용 프로그램의 구성 요소를 구성합니다.
## copilot cli install
사용하시는 시스템에 맞춰서 설치해주세요.
Mac
```bash=
brew install aws/tap/copilot-cli
```
Linux x86 (64-bit) 시스템:
```bash=
sudo curl -Lo /usr/local/bin/copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux \
&& sudo chmod +x /usr/local/bin/copilot \
&& copilot --help
```
Linux ARM 시스템:
```bash=
sudo curl -Lo /usr/local/bin/copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux-arm64 \
&& sudo chmod +x /usr/local/bin/copilot \
&& copilot --help
```
git branch 체크아웃
test환경으로 배포할 코드는 dev에서 시작합니다.
```bash=
git checkout -b dev
```
## Deploy ECS
```bash=
copilot init
```
설정은 아래와같습니다.
```bash=
--name api
--type 'Load Balanced Web Service'
--dockerfile './Dockerfile'
--port 80
```
입력한 출력은 아래와 같습니다.
```bash=
Note: It's best to run this command in the root of your Git repository.
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with an application on ECS. An application is a collection of
containerized services that operate together.
Use existing application: No
Application name: demo
Workload type: Load Balanced Web Service
Service name: api
Dockerfile: ./Dockerfile
no EXPOSE statements in Dockerfile ./Dockerfile
Port: 80
Ok great, we'll set up a Load Balanced Web Service named api in application demo listening on port 80.
✔ Created the infrastructure to manage services under application demo.
✔ Wrote the manifest for service api at copilot/api/manifest.yml
Your manifest contains configurations like your container size and port (:80).
✔ Created ECR repositories for service api.
All right, you're all set for local development.
Deploy: Yes
✔ Created the infrastructure for the test environment.
- Virtual private cloud on 2 availability zones to hold your services [Complete]
- Virtual private cloud on 2 availability zones to hold your services [Complete]
- Internet gateway to connect the network to the internet [Complete]
- Public subnets for internet facing services [Complete]
- Private subnets for services that can't be reached from the internet [Complete]
- Routing tables for services to talk with each other [Complete]
- ECS Cluster to hold your services [Complete]
✔ Linked account aws_account_id and region region to application demo.
✔ Created environment test in region region under application demo.
Environment test is already on the latest version v1.0.0, skip upgrade.
[+] Building 0.8s (7/7) FINISHED
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 37B 0.0s
=> [internal] load metadata for docker.io/library/nginx:latest 0.7s
=> [internal] load build context 0.0s
=> => transferring context: 32B 0.0s
=> [1/2] FROM docker.io/library/nginx@sha256:aeade65e99e5d5e7ce162833636f692354c227ff438556e5f3ed0335b7cc2f1b 0.0s
=> CACHED [2/2] COPY index.html /usr/share/nginx/html 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:3ee02fd4c0f67d7bd808ed7fc73263880649834cbb05d5ca62380f539f4884c4 0.0s
=> => naming to aws_account_id.dkr.ecr.region.amazonaws.com/demo/api:cee7709 0.0s
WARNING! Your password will be stored unencrypted in /home/user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
The push refers to repository [aws_account_id.dkr.ecr.region.amazonaws.com/demo/api]
592a5c0c47f1: Pushed
6c7de695ede3: Pushed
2f4accd375d9: Pushed
ffc9b21953f4: Pushed
cee7709: digest: sha_digest
✔ Deployed api, you can access it at http://demo-Publi-1OQ8VMS2VC2WG-561733989.region.elb.amazonaws.com.
```
# CI/CD pipeline
## Make repository
파이프라인을 시작할 repository를 만들겠습니다. codecommit에서 레포지토리를 생성합니다.
만약 Github나 다른 repository를 사용하시려면, 이 스텝은 건너뛰고 git init, 또는 clone 후 파이프라인 생성시 연결할 repository를 선택하면됩니다.
```bash=
sudo yum install jq
repo_https_url=$(aws codecommit create-repository \
--repository-name "ecsdemo" \
--repository-description "ECSWorkshop application" | jq -r '.repositoryMetadata.cloneUrlHttp')
```
### Configure git client to use the aws cli credential helper
```bash=
git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.UseHttpPath true
```
### Add the new repo as a remote
```bash=
git remote add cc $repo_https_url
```
### Push the changes
```bash=
git push cc HEAD
```
## pipeline 생성
```bash=
copilot pipeline init
copilot pipeline deploy
```
### SessionManager Plugin 설치
```bash=
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" -o "session-manager-plugin.rpm"
sudo yum install -y session-manager-plugin.rpm
```
### ECS exec
Docker exec명령어와 마찮가지로 직접 fargate의 컨테이너로 접근합니다.
```bash=
aws ecs -- \
--cluster cluster-name \
--task-definition task-definition-name \
--enable-execute-command \
--service-name service-name \
--desired-count 1
```
# 브랜치 전략

Dev에서 commit을 push하면 배포가 잘되는데...
그럼 이제 Main으로 Merge한 코드는 어떻게 배포를 할까요?

## Make Production
copilot env init
환경의 이름은 production으로 합니다. 같은 방법으로 pipeline도 생성합니다. svc와 env는 새로만든 production을 선택합니다.
## Next?
test때 생성했던 방법을 사용합니다.
# 추가 작업 내용
## Manual CI 파이프라인 만들기
1. fargate cluster 생성
2. ECR 생성
3. CodeCommit repository 생성 또는 (Github public repository)
4. 작업 폴더에서 git set-url cc "repo-url" 을 입력합니다.
5. 새로운 레포지토리에 새 코드가 push되는지 확인합니다.
```bash=
git add .
git commit -m "init repository"
git push
```
### 파이프라인 작업하기
이제 코드를 받은다음 docker build를 수행하여 ECR에 push하는 파이프라인을 만들어보겠습니다.
1. 콘솔의 CodePipeline에 접속합니다. 파이프라인 생성 버튼을 클릭합니다.
2. 파이프라인 이름을 입력하고 다음을 클릭합니다.
3. 소스공급자에 사용할 레포지토리를 입력합니다.(생성한 repo, main)
4. 빌드 스테이지에 CodeBuild를 선택합니다.
5. 프로젝트 생성을 누른 후 프로젝트 구성 이름을 입력하고 docker images는 standard, Amazon Linux:4.0를 클릭합니다.
6. "도커 이미지를 빌드하거나 빌드의 권한을 승격하려면 이 플래그를 활성화합니다."를 꼭 체크합니다.
7. 코드 파이프라인으로 계속을 클릭합니다.
8. CodeBuild스테이지에 환경변수를 입력할 것입니다. 변수를 buildspec에 입력하지 않고 환경변수로 뺀 이유는, 코드빌드 프로젝트를 다른 파이프라인에서도 재사용하기 위함입니다.
- AWS_DEFAULT_REGION : "us-west-2"
- AWS_ACCOUNT_ID : "Account number"
- IMAGE_REPO_NAME : "ECR repository name"
9. 입력을 완료하고 다음을 클릭합니다.
10. 배포스테이지는 건너뜁니다. 푸쉬가되는걸 확인하고 이어나가겠습니다. 파이프라인 생성을 완료하고 파이프라인이 잘 동작하는지 확인해봅니다.
11. 동작이 실패한 이유가 무엇일까요?
### Buildspec.yml 추가하기
루트 디렉토리에 buildspec.yml파일을 추가합니다.
```yml=
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $IMAGE_REPO_NAME:latest .
- docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
- docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo Writing image definitions file...
- printf '[{"name":"web-page","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinitions.json
artifacts:
files:
- imagedefinitions.json
```
내용은 단순합니다. 천천히 살펴보고 다시 push를 해보겠습니다.
이 yaml파일을 참조하면 gitlab pipeline, github action등 다른 3rd party CI도구에도 응용할 수 있습니다.
ECR의 생성한 레포지토리에가서 컨테이너 이미지가 올바르게 push되었는지 확인합니다.
## ADOT for ECS
- https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/monitoring/deploy-container-insights-ECS-adot.html
- https://aws-otel.github.io/docs/setup/ecs
# 참고자료
Spring Sample : https://github.com/spring-guides/gs-spring-boot-docker
Copilot primer Workshop : https://catalog.us-east-1.prod.workshops.aws/workshops/d03316be-3c29-49db-8dc3-eb196c1778c9/en-US/chapter4/content2
Workshop for CI/CD Workshop : https://catalog.us-east-1.prod.workshops.aws/workshops/cbcd960c-a07b-40c2-a01d-1d2e7a52b945/ko-KR
현대적 애플리케이션을 위한 컴퓨팅 인프라 활용 전략 - 이창익, AWS
: https://www.youtube.com/watch?v=oWty7PTvwBE
AWS에서 컨테이너 시작하기 : https://www.youtube.com/watch?v=wxyyUrWVBZA&t=2s