git repository: https://github.com/junchanpp/cicd-test
그림과 같이 구상할 예정(각각의 역할에 대해서는 https://keencho.github.io/posts/aws-cicd-1/ 참고 후 자세한 설명은 인터넷 검색 추천, NAT gateway는 유료 서비스이므로, ap-norteast-2a에만 배치할 예정, 다른 가용영역에 있더라도, 라우팅테이블을 통해서 매핑되므로 문제없음)
NAT gateway같은 경우, NAT instance로 대체가 가능. 더 싸지만, 장애 발생 시, 책임의 소지가 본인에게 있음.
- https://keencho.github.io/posts/aws-cicd-1/를 참고하여 설정하여도 좋고, 아래와 같이 설정해도 좋다.
- 생성할 리소스를 VPC등을 선택하여 한 번에 vpc 외의 subnet, routing table, internet gateway, NAT gateway가 생성되도록 했다.
- IPv4 CIDR 블록은 스터디 중 network 시간에 배운 대로 권장사항으로 입력하였음.
- 우리가 인스턴스로 이용할 t2-micro는 가용영역을 ap-northeast-2a와 ap-northeast-2c만 지원하므로(b,d는 지원 x) 인스턴스가 올라갈 수 있는 2a,2c로 지정했음
- 퍼블릭 서브넷과 프라이빗 서브넷의 역할은 블로그 참고(간단하게 설명하면, 보안적인 이유로 분리)
- NAT 게이트웨이는 유료이므로 하나만 설정
- VPC 엔드포인트 관련 설명은 아래 참고. 간단히 설명하자면, aws 내부서비스는 NAT gateway를 사용하지 않음으로써 비용 절약
- 결과
- 위에 설명한대로 구성하면 routing 테이블이 조금 다를텐데, 기본 라우팅 테이블을 public으로 변경 후, 기존의 기본 라우팅 테이블을 삭제하고, private 라우팅 테이블은 하나로 합쳤음.
- S3 Access Policy(autoscaling 그룹을 통해 생성된 EC2에서 사용, deploy script를 가져오는 용도)
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:Get*",
"s3:List*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
- ECR Access Policy(위와 같은 EC2에서 사용, 배포할 was의 image를 가져오는 용도)
- ECR Public은 서울은 지원안하므로, region은 버지니아 북부 추천(Private은 상관없는거같기도)
- 나머지는 상황에 맞게 추천
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GrantSingleImageReadOnlyAccess",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:DescribeImages",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:${region}:${aws_id}:repository/${img_name}"
},
{
"Sid": "GrantECRAuthAccess",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}
- CodeDeploy Auto Scaling Policy(CodeDeploy에서 Auto Scaling에 접근할 수 있는 권한 정책)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:PassRole",
"ec2:CreateTags",
"ec2:RunInstances"
],
"Resource": "*"
}
]
}
-
Auto Scaling Role(위에서 만든 S3관련 Policy와 ECR관련 Policy를 연결)
-
CodeDeploy Auto Scaling Role(CodeDeploy 앱에게 권한을 주는 role, 위에서 만든 CodeDeploy Auto Scaling policy와 AWS에서 기본적으로 제공하는 AWSCodeDeployRole를 연결)
- 위와 같이 codeDeploy를 선택해야 함
- Elastic Container Registry 검색 후 public registry 생성 후 기본별칭을 기억해주세요.(마지막에 코드 작성할 때 쓰임)
- 그 후, 퍼블릭 리포지토리 생성
- 버킷 생성 후 이름만 지어주고 기본 설정으로 생성
Amazon Machine Image, autoscaling group으로 생성될 ec2의 이미지
아래와 같이 인스턴스 생성
- 인스턴스 생성
- 키페어 생성 후 사용
- 네트워크 설정은 새로 만든 VPC에 연결하여, 빠르게 템플릿을 만들기 위해 public영역의 subnet 선택.
-
보안 그룹은 필요한 서비스를 설치하기 위해 빠르게 ssh만 허용하고 인스턴스 생성
-
템플릿의 ec2 설정
우선 ec2에 접속(안되면 keypair의 권한을 실행가능하게 설정해주세요)
sudo ssh -i ${keypair} ec2-user@${public_server_ip}
-
도커 설치
//먼저 yum update부터 sudo yum update // 도커 설치 sudo yum install docker -y // 도커 실행 sudo service docker start // 도커 상태 확인(확인 후 control+c) systemctl status docker.service //ec2-user에게 권한 부여 sudo usermod -aG docker ec2-user //권한 부여 확인 cat /etc/group
- ec2-user에게 도커 권한 부여 필요
-
EC2 CodeDeploy-Agent 설치
codeDeploy-agent는 Ruby로 작성되었기 때문에 이를 EC2 에서 실행하기 위해 Ruby 패키지를 설치해주어야 함.
sudo yum install ruby -y sudo yum install wget -y
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install chmod +x ./install
다운로드한 install 파일을 실행하여 codedeploy-agent 를 설치
📌 CodeDeploy Agent 설치 이슈
공식문서에 따르면, 현재 Ubuntu 20.04 이상 버전에서 codedeploy-agent 설치 시 이슈가 있어 설치 과정의 출력을 임시 로그 파일에 작성하여 해결한다고 합니다.
sudo ./install auto > /tmp/logfile
CodeDeploy 데몬 실행 확인
- codedeploy-agent 가 정상적으로 실행되고 있는지 확인합니다.
sudo service codedeploy-agent status
- 아래와 같은 문구가 출력되면 정상적으로 실행되고 있다는 의미입니다.
"The AWS CodeDeploy agent is running"
- 인스턴스가 부팅될 때마다 codedeploy-agent 가 자동 실행되도록 쉘스크립트 파일을 작성합니다.
$ sudo vim /etc/init.d/codedeploy-startup.sh
- 내용은 아래와 같습니다.
#!/binsudo service codedeploy-agent restart
- 해당 파일에 실행 권한을 부여하고 완료합니다.
sudo chmod +x /etc/init.d/codedeploy-startup.sh
-
ami 생성
ec2에서 설정해야 할 기본 설정은 끝.
- 위 옵션을 통해 이미지 생성
- 이름 적은 후 별다른 설정 없이 이미지 생성
-
사실 위에서 인스턴스에서 템플릿 생성해도 상관 없으나, 이미지를 만든 후 이미지를 통해 시작 템플릿 생성해보았음.
- 해당 메뉴로 접근(화면에 보이고 있는 이미 생성된 템플릿은 무시해주세요)
- 이름은 간단히 지은 후, ‘애플리케이션 및 OS 이미지’에서 내 AMI > 내 소유 > 방금 만든 이미지 를 선택
- 인스턴스 유형 및 키페어 미리 선택
- subnet은 auto scaling에서 지정할거기 때문에 여기서는 선택하지 않는다.
- 보안 규칙은 ssh 포트와 spring 포트를 허용
- 그 후, 맨 아래에 고급 세부 정보에서 IAM 인스턴스 프로파일에서 위에서 만든 IAM Role인 AutoScalingRole 선택
ec2 > 로드밸런싱 > 대상 그룹 선택
- 대상 유형 선택 > 인스턴스 선택
- 위와 같이 적어준다. 포트번호같은 경우, spring을 사용할거기 때문에 8080 이용
- 상태검사 경로는 spring-actuator 사용할 예정이라 위와 같이 입력(걱정마세요… spring boot 프로젝트에 패키지 설치만 하면 자동으로 설정되는 api. 자세한 내용은 검색)
- 고급 상태 검사 설정에서는 빠른 확인을 위해 값을 조금 조정. 그 후 다음 > 대상 그룹 생성
로드밸런서 생성 > Application Load Balancer 생성 선택
- 체계는 인터넷 경계를 선택해야 외부와 private subnet이 통신 가능
- 네트워크 매핑은 public subnet 선택
-
리스너는 주소를 입력할 때 8080을 입력하는 귀찮음을 줄이기 위해 80으로 적음(load balancer에 의해 자동으로 80포트에 대한 접근이 targetGroup으로 인해 private subnet에서는 8080으로 요청이 들어옴)
-
그후 로드밸런서 생성
ec2 > auto scaling group > auto scaling group 생성 선택
- 만들어두었던 시작 템플릿 선택
-
네트워크에서 서브넷을 private subnet으로 선택해준다 → private subnet에 spring server 배포
-
auto scaling이 잘되는지 확인하기 위해 원하는 용량은 2로 설정. 그후 별다른 설정없이 생성 완료
-
이름을 정해주고, 컴퓨팅 플랫폼은 EC2/온프레미스 선택
-
배포 그룹 이름을 짓고, 서비스 역할은 위에서 만든 CodeDeployAutoScalingRole을 선택한다.
-
배포 유형은 블루/그린 전략을 선택한다(현재 위치 전략은 rolling 배포 전략으로, 검색하면 차이점이 나옵니다.)
-
환경구성은 Amazon EC2 Auto Scaling 그룹 자동 복사 선택← 블루,그린 전환할 때 마다 AutoScaling Group도 블루, 그린으로 나뉘어서 생성됨.
-
로드밸런서의 대상그룹은 만들어둔 대상 그룹 선택
-
우선 repository를 하나 생성하자
-
프로젝트 최상단에 appspec.yml 생성
version: 0.0 os: linux files: - source: / destination: /home/ec2-user/app overwrite: yes permissions: - object: / pattern: "**" owner: ec2-user group: ec2-user mode: 755 hooks: AfterInstall: - location: scripts/deploy.sh timeout: 60 runas: ec2-user
- files에서 선언한 코드로 인해 S3에 업로드된 zip 파일의 압축을 풀어 /home/ec2-user/app으로 동
- 이동시키기 전에 파일들의 권한 설정
- hooks는 codeDeploy를 통해 ec2에 다운받은 파일을 실행한다.
-
프로젝트 최상단에 Dockerfile 작성
FROM openjdk:17-oracle AS builder RUN microdnf install findutils COPY gradlew . COPY settings.gradle . COPY build.gradle . COPY gradle gradle COPY src src COPY backend-config backend-config RUN chmod +x ./gradlew RUN ls -la RUN ./gradlew build FROM openjdk:17-oracle RUN mkdir /opt/app COPY --from=builder build/libs/*.jar /opt/app/spring-boot-application.jar EXPOSE 8080 ENV PROFILE local ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=${PROFILE}" ,"/opt/app/spring-boot-application.jar"]
-
.github/workflows/aws.yml(파일이름은 상관없음) 생성 후 아래 내용 작성
name: Deploy to dev env on: push: branches: [ dev ] jobs: build-docker: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v2 - name: setup jdk 17 uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: '17' - name: add permission to gradlew run: chmod +x ./gradlew shell: bash - name: aws configure uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 #본인의 리전 입력. 퍼블릭이면 아마 서울이 안돼서 버지니아 북부로 설정 - name: Login to ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 with: registry-type: public - name: build docker file and setting deploy files env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: #본인 레파지토리의 이름 ECR_REGISTRY_ALIAS: #본인의 레지스트리의 별칭 IMAGE_TAG: ${{ github.sha }} run: | docker build -t $ECR_REGISTRY/$ECR_REGISTRY_ALIAS/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REGISTRY_ALIAS/$ECR_REPOSITORY:$IMAGE_TAG echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT mkdir scripts touch scripts/deploy.sh echo "aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin public.ecr.aws/$ECR_REGISTRY_ALIAS" >> scripts/deploy.sh echo "docker pull public.ecr.aws/$ECR_REGISTRY_ALIAS/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh echo "docker run -p 8080:8080 -e PROFILE=dev -d --restart always --name test public.ecr.aws/$ECR_REGISTRY_ALIAS/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh - name: upload to s3 env: IMAGE_TAG: ${{ github.sha }} run: | zip -r deploy-$IMAGE_TAG.zip ./scripts appspec.yml aws s3 cp --region {s3의 region} --acl private ./deploy-$IMAGE_TAG.zip s3://{s3의 버킷} - name: start deploy env: IMAGE_TAG: ${{ github.sha }} run: | aws deploy create-deployment --region {codeDeploy의 region} \ --application-name {codeDeploy 이름} \ --deployment-config-name CodeDeployDefault.OneAtATime \ --deployment-group-name {codeDeployGroup 이름} \ --s3-location bucket={s3의 버킷},bundleType=zip,key=deploy-$IMAGE_TAG.zip
이제 dev 브랜치로 push하면 자동 배포가 됩니다.
-
Github actions 탭을 확인해보면 일정시간 이후 완료된 것을 알 수 있음. 하지만 code deploy의 프로세스에서는 현재 이곳에서 확인 불가. aws에서 확인 가능
-
배포 중임을 확인할 수 있음
- 네트워크 매핑 과정에서 가용영역을 ap-northeast-2a,2b로 잘못 선택했다. t2-micro는 ap-northeast-2a,2c만 지원하고, 2b와 2d를 지원하지 못한다는 것을 몰랐다. 그로 인해서인지, 2b에는 배포가 되지 않았다. → 하지만 이것도 정확한 원인파악을 하지 못해 파악 필요 → 글을 작성하며 해결했습니다.
- 네트워크 구성을 잘 하지 못했다. public과 private subnet을 제대로 나누지 못했고, 우선 public subnet에서만 진행했다. 네트워크 구성을 실제 서비스 운영 서버로 생각하고 다시 구성하여 CICD를 구현해보고 싶다. → 글을 작성하면서 구별지었습니다.
- CICD를 한 번 진행하는데에 너무 오랜 시간이 걸린다. CI(github actions)가 2분정도 걸리고, CD가 8분정도 걸린다. 시간을 더 단축해보고 싶다. 또한 github actions를 통해서 CD의 진행과정이 표시되지 않는다. CICD의 진행과정을 한 눈에 보도록 구성하고 싶다. → https://www.jongho.dev/aws/aws-codedeploy-speed-up/ 줄일 수 있는 방법을 확인했습니다. 글에는 적지 않았지만, 수정해볼 예정입니다. -> toy-project에서 다른 방식을 사용하여(정석은 아닌 것 같지만) 해결하였습니다.
그래도 진행하면서 autoscaling이나, code deploy, github actions, load balancer의 이해도는 확실히 높아진 것 같다 !
https://keencho.github.io/posts/aws-cicd-1/ https://velog.io/@kshired/Github-Actions-ECR-Auto-Scaling-Group-EC2-CodeDeploy-S3-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-BlueGreen-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0#auto-scaling-role https://velog.io/@ch4570/Github-Actions-Nginx%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-CICD-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B5%AC%EC%B6%95-EC2-S3-%EC%84%A4%EC%A0%95#-ec2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0