EKS 에서의 CI / CD 를 고민중이다. AWS 리소스만을 이용하는 방법, 범용적인 CI/CD 를 사용하는 방법... ArgoCD, TerraForm, Jenkins 등 뭐가 많지만 되도록 AWS 리소스를 사용해 보기로 했다. 예전에 ECS 배포할 때는 gradle 에서 Docker 이미지 생성 / ECR 업로드 / 배포까지... 전부 해결했었다. 로컬에서 태스크만 실행하면 되니 build.gradle 만 기다랗게 작성하는 것 빼고는 별 문제가 없었다. 그 때의 애로사항은 배포하려는 개발자의 PC 에 Docker 가 실행되고 있어야 한다는 점. ECR 인증키가 git 에 포함된다는 점. 배포시 오류가 발생한다면 즉시 파악이 어려운 점... 트집을 잡자면 그 정도. 하지만 대부분의 개발자가 docker 를 사용하고, git 도 프라이빗, 배포시 오류는 알림으로 처리했었고... 라며 흡족하게는 썼었지만, 이번엔 왠지 도구를 이용해야만 할 것 같은 느낌적인 느낌. 시간 있으면 이것저것 다 써보는게 좋은거지. 싶어서 CodeCommit, CodeBuild, CodePipeline 을 사용해 보았다.
사용할 AWS CI/CD Resource
CodeCommit - git 저장소 / 소스 위치
CodeBuild - 소스코드를 가져와 Test/Build 실행. - 자체 컴퓨팅으로 docker 이미지 생성, ECR 이미지 업로드, EKS 배포에 관한 빌드사양 파일 작성(buildspec.yml)
CodePipeline - git 저장소에 소스코드 변경이 감지되었을 때, source 아티팩트 S3 업로드 후 CodeBuild 실행
또한, 위 사양처럼 REPO_ECR 등의 프라이빗 정보들은 빌드 프로젝트 구성시 환경변수로 지정한 변수명으로 사용할 수 도 있다. echo 나 이미지 태깅 관리 때문에 조금 길어지긴 했는데 필요에 맞게 사용자 정의...
CodePipeline 생성
AWS 관리콘솔에서 파이프라인 생성
역할 이름 : 여러 파이프라인을 관리한다면 하나로 재사용
아티팩트가 업로드 될 버킷 위치 수동 지정시
소스 스테이지 : CloudWatch 로 변경 감지, 출력 아티팩트 형식은 기본값을 사용하여 zip 형식의 데이터를 사용해도 무방하지만 간단히 메타데이터만 전달하는 전체 복제 선택.(gitpull 권한 추가 필요)
빌드 스테이지 추가 후 배포 스테이지 건너뛰기
배포 테스트
CodePipeline 이 생성되면 즉시 실행이 되는데, 아니면 수동으로 빌드하던지, git 소스를 푸시하던지... 해서 배포 롤링 업데이트 확인까지. 이제부터는 에러의 향연이다. CodeBuild 의 플로우를 보면 거의 모든 단계에 해당 리소스에 대한 권한이 필요하므로 생성한/된 codebuildrole 에 각 권한을 추가해 주면 된다.
마치 일부러 에러를 찾아내려는 듯한 치밀한 실수들이 많았다; awscli 버전이 1.2 라 추후 문제가 있을수도 있지 않을까 하는 걱정도 되고... CodeCommit, CodeBuild, CodePipeline, ECR, EKS 등 AWS 리소스 만으로 CI/CD 를 구성해 본 느낌은... 그냥 그렇다.ㅋ 뭔가 자동화 스러우면서도, 관리 포인트가 점점 늘어나는 찝찝한 느낌... 남은건 구성 완료에 대한 약간의 성취감? ㅋ
: 빌드 프로젝트 새로 구성시 한번이라도 validation 에 실패하면 재검증시 터무니없는 메시지가 출력됨. 사실 권한 및 정책이 검증과정에서 이미 생성되는데 같은 이름의 권한 및 정책이 존재한다는 메시지가 올바르나... 아무튼 자동 생성된 권한 및 정책을 삭제해도 마찬가지이고... 권한 및 삭제 뒤 빌드 프로젝트를 한번에 새로 구성하면 해결; (한번이라도 검증 실패시 아래 메시지 조우)
서비스 역할 이름이 잘못되었습니다. 유효한 문자는 꼬부랑꼬부랑 !@#$ㄲㅉㄸㄲㅉ
S3 소스 다운로드시 권한 없음
: AmazonS3ReadOnlyAccess 권한 추가로 해결
Waiting for DOWNLOAD_SOURCE
AccessDenied: Access Denied
status code: 403, request id: VX7AKNM69P3N7123, host id: 123y3WpjIvTzVViVzHclfn4F3UfDg4mJA++8BSY6q0jQyEPCWr4cohc0dVJ+q08N/123LMNToEI= for primary source and source version arn:aws:s3:::source-artifact-temp/example/SourceArti/BwUEum1
buildspec.yml 경로 에러 발생
: 경로 확인으로 해결
Phase context status code: YAML_FILE_ERROR Message: stat /codebuild/output/src123816321/src/git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/example/Api/buildspec.yml: no such file or directory
ecr 로그인 에러
: AmazonEC2ContainerRegistryPowerUser 권한 추가로 해결
Running command aws --version
aws-cli/1.20.58 Python/3.9.5 Linux/4.14.252-195.483.amzn2.x86_64 exec-env/AWS_ECS_EC2 botocore/1.21.58
Running command $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
An error occurred (AccessDeniedException) when calling the GetAuthorizationToken operation: User: arn:aws:sts::123151105123:assumed-role/exampleCodeBuildServiceRole/AWSCodeBuild-ccf3e123-4123-4123-9ba1-1231fc6797b8 is not authorized to perform: ecr:GetAuthorizationToken on resource: * because no identity-based policy allows the ecr:GetAuthorizationToken action
Command did not exit successfully $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email) exit status 255
Phase complete: PRE_BUILD State: FAILED
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email). Reason: exit status 255
gradle 구문 에러
: 기본 gradle 버전 호환 오류로 스크립트 수정으로 해결 gradle build -> ./gradlew build
Running command gradle --version
------------------------------------------------------------
Gradle 5.6.4
------------------------------------------------------------
...
* What went wrong:
An exception occurred applying plugin request [id: 'org.springframework.boot', version: '2.6.0']
> Failed to apply plugin [id 'org.springframework.boot']
> Spring Boot plugin requires Gradle 6.8.x, 6.9.x, or 7.x. The current version is Gradle 5.6.4
Command did not exit successfully gradle :Api:build exit status 1
Phase complete: BUILD State: FAILED
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: gradle :Api:build. Reason: exit status 1
Dockerfile 경로 에러
: 경로 수정으로 해결 . -> ./AWS/Dockerfile
Running command docker build -t $REPOSITORY_URI:latest .
unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /codebuild/output/src368940192/src/git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/kps/Dockerfile: no such file or directory
eks cluster 조회 권한 에러
: AmazonEKSWorkerNodePolicy 권한 수정 및 바인딩 추가로 해결
Running command aws eks update-kubeconfig --region ap-northeast-2 --name example
An error occurred (AccessDeniedException) when calling the DescribeCluster operation: User: arn:aws:sts::123151105123:assumed-role/exampleCodeBuildServiceRole/AWSCodeBuild-123e955a-545c-42c6-8d21-67215aef0123 is not authorized to perform: eks:DescribeCluster on resource: arn:aws:eks:ap-northeast-2:123151105123:cluster/example
Command did not exit successfully aws eks update-kubeconfig --region ap-northeast-2 --name example exit status 255
Phase complete: INSTALL State: FAILED
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: aws eks update-kubeconfig --region ap-northeast-2 --name example. Reason: exit status 255
kubectl 권한 에러
: 사용자나 역할에 대한 aws-auth ConfigMap 수정으로 해결
Running command kubectl get po -n namespace1
error: You must be logged in to the server (Unauthorized)
Command did not exit successfully kubectl get po -n namespace1 exit status 1
Phase complete: INSTALL State: FAILED
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: kubectl get po -n namespace1. Reason: exit status 1
Error from server (Forbidden): error when retrieving current configuration of:
Resource: "apps/v1, Resource=deployments", GroupVersionKind: "apps/v1, Kind=Deployment"
Name: "my-deploy", Namespace: "namespace1"
from server for: "./AWS/example-deployment.yaml": deployments.apps "my-deploy" is forbidden: User "system:node:AWSCodeBuild-fccb1123-1235-49c9-1238-b11cf05bc0c8" cannot get resource "deployments" in API group "apps" in the namespace "namespace1"
Running command kubectl rollout restart -n example-namespace deployment my-deploy
error: failed to patch: deployments.apps "my-deploy" is forbidden: User "system:node:AWSCodeBuild-a0f27123-b1fc-4e12-1e3f-0eac123f5123" cannot patch resource "deployments" in API group "apps" in the namespace "namespace1"
Command did not exit successfully kubectl rollout restart -n namespace1 deployment my-deploy exit status 1
Phase complete: POST_BUILD State: FAILED
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: kubectl rollout restart -n namespace1 deployment my-deploy. Reason: exit status 1
$ kubectl apply -f eks-console-full-access.yaml clusterrole.rbac.authorization.k8s.io/eks-console-dashboard-full-access-clusterrole created clusterrolebinding.rbac.authorization.k8s.io/eks-console-dashboard-full-access-binding created
ALB 생성 역시 NLB 와 흡사하지만 ingress 리소스를 생성해야 하고, NodePort 나 LoadBalancer 타입의 Service 를 생성하는 것이 다르다. 마찬가지로 nginx 의 첫화면을 확인하는 샘플. 어플리케이션 배포는 NLB 테스트에 사용한 것과 동일, ALB / NodePort 정상 구동 확인하고, 브라우저에서 nginx 확인하고...
1. Sample namespace
$ kubectl create namespace alb-sample-app
& 해당 namespace 로 fargate 프로파일도 작성
2. nginx Application, NLB 생성
로드밸런서의 타입인 Instance 와 IP 중 Fargate 는 IP 타겟만 지정 가능하다. Instance 는 node 로, IP 는 pod 로 라우팅된다. 아래 스크립트는 선택기가 app=nginx 인 pod 로 라우팅하는 ALB 를 생성한다.
$ kubectl apply -f sample.yaml
Deployment/sample-app created
Service/sample-service-nodeport created
Ingress/sample-ingress created
3. 서비스 확인
$ kubectl get svc sample-service-nodeport -n alb-sample-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
sample-service-nodeport NodePort 172.20.23.108 <none> 80:31517/TCP 4s
$ kubectl get ingress sample-ingress -n alb-sample-app
NAME CLASS HOSTS ADDRESS PORTS AGE
sample-ingress <none> * k8s-samplein-1234d81632-1363801234.ap-northeast-2.elb.amazonaws.com 80 3h43m
여기까지 진행하였으면, AWS 관리콘솔에서 [로드밸런서] 와 [대상 그룹] 이 정상적으로 구동되었는지 확인한다.
로드밸런서 : DNS 이름 / application 유형 / internet-facing 체계
대상그룹 : 80 port / HTTP protocal / IP target / Health status
4. 결과 확인
상단 ingress DNS 주소로 curl 이나 browser 를 사용하여 nginx 첫 페이지를 확인한다.
$ kubectl apply -f sample-service.yaml
Service/nlb-sample-service created
4. 서비스 확인
$ kubectl get svc nlb-sample-service -n nlb-sample-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
sample-service LoadBalancer 10.100.240.137 k8s-nlbsampl-nlbsampl-xxxxxxxxxx-xxxxxxxxxxxxxxxx.elb.us-west-2.amazonaws.com 80:32400/TCP 16h
여기까지 진행하였으면, AWS 관리콘솔에서 [로드밸런서] 와 [대상 그룹] 이 정상적으로 구동되었는지 확인한다.
로드밸런서 : DNS 이름 / network 유형 / internet-facing 체계
대상그룹 : 80 port / TCP protocal / IP target / Health status
5. 결과 확인
상단 EXTERNAL-IP 의 DNS 주소로 curl 이나 browser 를 사용하여 nginx 첫 페이지를 확인한다.