[CI] CronJob으로 ECR 인증 Secret Update 하기

우노·2024년 11월 15일
0

Practice & Trouble Shooting

목록 보기
19/20

앞선 글에서 Spot Instance로 Jenkins Kubernetes cloud agent 구성했다. 하지만 현재 EKS NodeGroup으로 설정한 모든 노드는 Docker 설치가 되어있지 않은 기본 제공 AMI를 쓰기 때문에 기존에 활용하던 Docker 플러그인 활용이 어려워져 Kaniko로 이미지 빌드와 푸시를 수행하게 되었다.

문제 상황

  - name: kaniko
    image: gcr.io/kaniko-project/executor:debug
    command:
    - sleep
    args:
    - infinity
    volumeMounts:
    - name: registry-credentials
      mountPath: /kaniko/.docker
  volumes:
    - name: registry-credentials
      secret:
        secretName: ecr-secret
        items: 
        - key: .dockerconfigjson
          path: config.json

Kaniko는 다음과 같은 설정으로 에이전트 파드의 컨테이너로써 동작한다. 여기서 문제가 생긴 부분은 secret이다.

EKS에서 파드를 실행할 때에 ECR에서 이미지를 가져오려면 NodeGroup에 IAM 정책을 적절히 설정하면 된다. 하지만 Jenkins Agent에서 띄운 kaniko 컨테이너에서는 IAM 정책으로 ECR 이미지를 가져오는 작업이 아닌 docker-registry secret으로 레포지토리에 직접 연결하여 이미지 빌드 푸시를 진행해야한다. 그래서 별도로 만들어준 docker-registry secret을 컨테이너에 마운트해서 해결하려고 했다.

🚧 ECR 인증 에러 발생 !

+ executor '--dockerfile=Dockerfile' 
	'--context=/workspace' '--destination=...' --cleanup
error checking push permissions 
	-- make sure you entered the correct tag name,
    	and that you are authenticated correctly, 
        and try again: checking push permission for "...": 
POST https://.../blobs/uploads/: DENIED: 
Your authorization token has expired. Reauthenticate and try again.

Amazon ECR의 프라이빗 레지스트리 인증 문서를 살펴보면

권한 부여 토큰은 IAM 보안 주체가 액세스하고 12시간 동안 유효한 Amazon ECR 레지스트리에 액세스하는 데 사용됩니다.

그렇다. aws-cli로 발급받은 ECR 패스워드가 만료되어 인증을 하지 못하는 상황이 발생한 것이었다.

에러가 뜰 때마다 패스워드 재발급을 몇 번 반복해보니 매우 번거로운 작업이었다.. 그래서 10~11시간마다 ECR 패스워드를 발급받아서 새롭게 secret으로 등록하는 작업을 자동화할 계획을 세웠고 일정 시간마다 쿠버네티스에서 동작하게 하기 위해서 CronJob으로 머리를 굴리기 시작했다!

0️⃣ CronJob 설계

일단 필요한 커맨드 라인 도구는 aws-clikubectl 2가지이다. 그리고 이걸 실행할 수 있는 aws-credentials와 kube-apiserver에 api 요청을 할 권한이 필요하다.

일단 큰 그림은 다음과 같다.

  • ubuntu 이미지 (가장 익숙한 linux)
  • 쉘 스크립트 실행
    • aws-cli, kubectl 설치
    • credentials 세팅 후에 ECR secret TOKEN 발급
    • kubectl apply 수행

그리고 이걸 10시간 정도에 한 번씩 실행해주면 된다.

1️⃣ ServiceAccount, RoleBinding

먼저 kubectl apply로 Secret을 수정할 수 있도록 ServiceAccount를 생성하고 적절한 Role을 생성하여 바인딩한다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ecr-secret-refresh
  namespace: devops-tools
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: devops-tools
  name: ecr-secret-refresh-role
rules:
  - apiGroups:
      - ""
    resources:
      - secrets
    verbs:
      - create
      - get
      - list
      - update
      - patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ecr-secret-refresh-binding
  namespace: devops-tools
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ecr-secret-refresh-role
subjects:
  - kind: ServiceAccount
    name: ecr-secret-refresh
    namespace: devops-tools

처음 만들었을 때는 patch가 없었는데 apply에 patch가 필요하다는 에러를 확인하고 추가하였다.

Resource: "/v1, Resource=secrets", GroupVersionKind: "/v1, Kind=Secret"
Name: "ecr-secret", Namespace: "devops-tools"
for: "STDIN": error when patching "STDIN": secrets "ecr-secret" is forbidden: User "system:serviceaccount:devops-tools:ecr-secret-refresh" cannot patch resource "secrets" in API group "" in the namespace "devops-tools"

2️⃣ Script

aws-cli, kubectl 설치, Token 발급 및 Secret 업데이트를 실행할 스크립트를 작성했다.

#!/bin/bash

AWS_REGION="ap-northeast-2"
AWS_ACCOUNT_ID="054037113048"
EMAIL="hnnynh125@gmail.com"
CLUSTER_NAME="eks-default"

echo "========== Installing AWS CLI... =========="
apt-get update && \
apt-get install -y curl unzip && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
echo "========== AWS CLI installed successfully =========="


echo "========== Installing kubectl... =========="
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
chmod +x kubectl && \
mv kubectl /usr/local/bin/ && \
echo "========== kubectl installed successfully =========="


echo "========== Retrieving ECR login password... =========="
TOKEN=$(aws ecr get-login-password --region $AWS_REGION)

echo "========== Creating and applying Kubernetes secret for ECR... =========="
kubectl create secret docker-registry ecr-secret \
    --docker-server=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \
    --docker-username=AWS \
    --docker-password=$TOKEN \
    --docker-email=$EMAIL \
    --namespace=devops-tools \
    --dry-run=client -o yaml | kubectl apply -f - && \  
echo "========== ECR secret applied successfully =========="

로그도 찍고 싶어서 중간중간 echo 출력을 많이 넣었다.

3️⃣ CronJob

마지막으로 스크립트를 실행할 CronJob을 구성해준다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: ecr-secret-refresh
  namespace: devops-tools
spec:
  schedule: "0 */10 * * *" # 10시간마다 실행
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: ecr-secret-refresh
          containers:
            - name: ecr-secret-refresh
              image: ubuntu:latest
              command:
                - /bin/bash
                - -c
                - |
                  chmod +x /scripts/refresh-ecr-secret.sh
                  /scripts/refresh-ecr-secret.sh
              volumeMounts:
                - name: script-volume
                  mountPath: /scripts
                - name: aws-config
                  mountPath: /root/.aws/config
                  subPath: config
                - name: aws-credentials
                  mountPath: /root/.aws/credentials
                  subPath: credentials
          restartPolicy: OnFailure
          volumes:
            - name: script-volume
              configMap:
                name: ecr-secret-refresh-script
                defaultMode: 0777 # ConfigMap Read-Only FileSystem Error Solution
            - name: aws-config
              secret:
                secretName: ecr-config
            - name: aws-credentials
              secret:
                secretName: ecr-credential

image 등을 수정하고 스크립트를 수정하면서 추가적으로 개선할 사항이 보이지만 지금은 이걸 설정해두고 해야할 일이 많기 때문에.. 우선 가장 간단하게 ubuntu:latest 이미지로 얼른 트러블슈팅을 마쳤다.

➕ Secret: config, credentials

volume을 확인하면 이 스크립트를 작성하기 전에 aws credential 세팅이 필요하다.

[default]
region = ap-northeast-2
[default]
aws_access_key_id = ...
aws_secret_access_key = ...

config와 credential을 이름으로 한 파일을 위와 같이 만들어두고 --from-file로 각각 secret을 생성해서 ubuntu 컨테이너의 .aws에 마운트하여 계정 정보를 세팅해주었다.

🚧 ConfigMap Read-Only Filesystem

원래 ConfigMap volume 아래의 defaultMode가 없었다. 하지만 그렇게 되면..

chmod: changing permissions of '/scripts/refresh-ecr-secret.sh': Read-only file system
/bin/bash: line 1: /scripts/refresh-ecr-secret.sh: Permission denied

chmod와 스크립트 실행이 모두 불가능하다. ConfigMap은 Read-Only FileSystem이어서 securityContext: 0으로 주고 미리 chmod +x 하고 configmap 만들어도 불가능하다.
그래서 찾은 방법은 defaultMode를 추가하여 파일의 권한을 설정해주는 것이었다. 모든 사용자와 그룹에게 읽기, 쓰기, 실행이 가능하게 설정했다. 이로써 securityContext를 0으로 실행하지 않아도 동작할 수 있게 되었다.

성공!

========== Creating and applying Kubernetes secret for ECR... ==========
secret/ecr-secret configured
========== ECR secret applied successfully ==========

Job이 성공적으로 Complete 되었고 파드의 로그를 확인했을 때에도 secret이 잘 업데이트 된 것을 확인할 수 있었다. Jenkins에서도 돌려보면서 업데이트된 secret를 kaniko에서 활용하는 것을 확인할 수 있었다.

추가 개선 사항

amazon/aws-cli 이미지를 활용하거나 aws-cli, kubectl이 미리 깔려있는 컴팩트한 이미지를 활용하면 시간과 불필요한 이미지 용량을 줄일 수 있을 것이다. 하지만 aws-cli 사용 경험이 없는 상황에서 shell script 트러블슈팅이 길어질 것 같았고 이미 ubuntu로 트러블슈팅 중에 발견하여 적용하지 못했다. 추가로 개선할 여지가 남아있을 것 같다.

그리고 Kubernetes에서 AWS ECR 인증 정보를 자동 갱신하기 등과 같은 다른 분들의 글을 참고했을 때 매니페스트를 작성할 수도 있지만 helm으로 관리할 수도 있겠다는 생각이 들었다. 당장은 조금 어렵겠지만 또 하면서 배워가고 싶다.

아마 문제를 해결할 수 있는 방법 중에 효율과 편의성에 관계없이 가장 쉽고 단순한 방법을 선택한 것 같다. 지식과 시간의 문제라고 생각해서 앞으로 발전시킬 방안을 더 생각해보고 싶다.

제 글이 조금이나마 도움이 되었으면 좋겠습니다. 읽어주셔서 감사합니다!

profile
기록하는 감자

0개의 댓글