DevOps & Infra/Kubernetes

[k8s/argocd] Helm과 ArgoCD, Github Actions, ECR를 활용한 배포 파이프라인 구축 (minikube)

턴태 2023. 11. 15. 22:34

오늘은 자동화의 꽃인 파이프라인을 구현해보도록 하겠습니다. 배포 자동화는 다양한 방법으로 구현할 수 있습니다. 예를 들어서, github actions로 beanstalk에 패키지를 전송하여 배포할 수도 있으며, code pipeline을 통해 빌드와 배포를 자동화할 수도 있고, github actions로 빌드한 파일을 code deploy에 전달하는 방법도 있습니다. 파이프라인을 추상적으로 보자면, 빌드 -> (테스트) -> 배포의 과정을 가지고 있습니다.

 

이번에는 kubernetes를 활용하는 환경에서 github action과 argocd를 활용해 빌드 및 배포를 자동화하는 방법을 구현합니다. 나름대로 구조를 구체화했을 때, 아래와 같은 형태를 따릅니다.

 

과정은 아래와 같습니다.

 

 

  1. 개발 내용 커밋/푸시: 개발자가 개발을 완료하면, 해당 서버 애플리케이션의 레포지터리로 커밋을 푸시합니다.
  2. CI 도구 트리거: 레포지터리의 변경사항을 자동으로 감지하거나, 특정 조건을 부과하여 CI 도구가 원하는 동작을 수행할 수 있도록 합니다. 본 예시에서는 github actions를 사용하므로 github actions가 특정 branch에 push가 발생했을 시에 기재한 동작을 수행할 수 있도록 했습니다.
  3. 이미지 Push: kubernetes 환경은 컨테이너로 동작하므로 이미지가 필수적입니다. 여기서 원하는 레지스트리에 빌드한 이미지를 push합니다. 예를 들어서, docker hub나 ecr registry 등이 있습니다.
  4. Manifest 수정: Manifest는 kubernetes 오브젝트 상세를 kubernetes에 적용시킬 때 필요한 파일입니다. 기본적으로 YAML 형태의 파일들이 있으며, 이를 지원해주는 Kustomize나 Helm을 사용하여 오브젝트를 적용시킬 수 있습니다. 애플리케이션 레포지터리에서 변경된 내용을 토대로 manifest 속 내용을 수정합니다. 예를 들어, 이미지 태그를 수정할 수 있습니다.
  5. Manifest 변경 사항 감지: argocd는 레포지터리의 변경사항을 감지합니다. 수동 혹은 자동으로 변경된 사항을 동기화할 수 있습니다. continuous delivery 도구지만, 자동으로 동기화하는 auto-sync 기능이 있어서 continuous deployment 도구의 성격도 가집니다. Helm을 사용한다면 Helm charts를 토대로 패키지를 적용시킵니다.
  6. 이미지 Pull: 마지막으로 이미지를 pull 받아 디플로이먼트를 생성합니다.

돈이 없고 m1을 사용하고 있어서 가상환경을 만들기 어려우므로 minikube를 사용하겠습니다.

 

그러면 GitHub 커밋 푸시부터 차근차근 전달드리겠습니다.

 

1️⃣ github actions workflow 작성 - CI

이번에는 CI로 사용할 도구로 github actions를 선택했습니다. 따로 설치할 필요도 없으며, 가장 간단하기 때문입니다.

 

먼저 workflow는 다음과 같습니다. 깃허브 액션 명세는 .github/workflows 디렉터리에 yaml 확장자로 생성합니다.

 

우리가 수행해야 할 작업들은 jobs 이하에 정리되어 있으며, ci라는 필드 아래에 통합되어 있습니다. 띵스플로우 블로그를 참고하였으며, 버전들과 일부 설정들을 수정했습니다. https://thingsflow.com/blog/143

name: Build Image And Push an Image to ECR(CI)

on:
  push:
    branches: [ main ]

permissions:
  id-token: write
  contents: write

jobs:
  ci:
    runs-on: ubuntu-latest
    outputs:
      IMAGE_TAG: ${{ steps.set-var.outputs.IMAGE_TAG }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ap-northeast-2
          role-to-assume: ${{ secrets.ARN_ECR_PUSH_ROLE }}
          role-session-name: ecrPrivatePushRole

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Set var
        id: set-var
        run: |
          echo ::set-output name=ECR_REGISTRY::${{ steps.login-ecr.outputs.registry }}
          echo ::set-output name=ECR_REPOSITORY::nodejs-server
          echo ::set-output name=IMAGE_TAG::$(cat VERSION)

      - name: Docker image Build
        id: build-image
        run: |
          docker build \
            -f Dockerfile \
            -t ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }} .

      - name: Docker image Push
        id: push-image
        run: |
          docker push ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }}

 

한 단계 씩 확인해보면 다음과 같습니다.

runs-on: ubuntu-latest
outputs:
  IMAGE_TAG: ${{ steps.set-var.outputs.IMAGE_TAG }}

 

Github actions가 작업을 수행할 os로는 ubuntu 최신 버전을 사용합니다. outputs는 변수를 저장하기 위해 생성한 필드입니다.

- name: Checkout
  uses: actions/checkout@v4

 

CI를 수행하기 위해 깃허브 레포지터리의 브랜치로 이동한 다음 코드 내용을 받습니다.

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-region: ap-northeast-2
    role-to-assume: arn:aws:iam::213360237800:role/Github@ECR_login_and_push_image
    role-session-name: ecrPrivatePushRole

 

aws credential을 저장합니다. 이후에 사용할 aws cli를 위해 필요합니다. IAM 계정의 access key와 secret key를 사용하는 방법도 있지만, 현재는 OIDC를 사용하는 방법을 권장하고 있습니다.

OIDC

OIDC는 OpenID Connect의 약자로 OpenID를 사용하는 프로토콜을 의미합니다. 간단하게 사용자의 신원을 확인할 수 있는 토큰을 발급받는 것입니다. 깃허브 액션에서 사용하는 aws 기밀 정보들은 그 액션 당시에만 잠깐 사용되는 값입니다. 따라서 굳이 access key와 secret 키를 사용할 필요 없이 단기간에 유효한 토큰을 사용하는 것이 더 효율적인 선택이 될 것입니다. 그래서 단기간의 AWS 자격 증명을 얻으려면 GitHub의 OIDC provider를 사용하는 것이 좋습니다.

 

OIDC를 사용하는 경우 이 작업은 워크플로 실행에 고유한 JWT를 만들고 이 JWT를 사용하여 역할을 맡게 됩니다. 여기서 JWT를 만들려면 workflow에 id 토큰에 대한 쓰기 권한이 있어야 합니다.

permissions:
  id-token: write
  contents: write

 

다음으로는 Github OIDC provider를 설정해야 합니다. AWS 콘솔의 IAM 에서 자격 증명 공급자 탭으로 이동하여 공급자 추가를 클릭합니다.

 

그 다음 공급자로 Github OIDC 공급자를 추가합니다.

 

공급자 url에는 https://token.actions.githubusercontent.com 를 입력하고 대상에는 sts.amazonaws.com 를 입력합니다.

AWS 정책 생성

정책을 생성하여 권한을 지정해줍니다. 저는 ecr에 로그인한 다음, push까지 할 예정이므로, 아래의 권한들이 필요합니다.

"ecr:GetAuthorizationToken"
"ecr:BatchCheckLayerAvailability"
"ecr:CompleteLayerUpload"
"ecr:InitiateLayerUpload"
"ecr:PutImage"
"ecr:UploadLayerPart"

 

어떻게 알았냐면, market place에서 확인했습니다. https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions

 

Amazon ECR "Login" Action for GitHub Actions - GitHub Marketplace

Logs in the local Docker client to one or more Amazon ECR Private registries or an Amazon ECR Public registry

github.com

마켓 플레이스에는 이런 유용한 정보들이 있답니다.

 

서비스로는 Elastic Container Registry를 선택해줍니다.

 

역할들을 각각 고르고 리소스는 간단하게 하기 위해 모두를 선택했습니다.

 

마지막으로 정책 이름과 설명을 작성해줍니다. 저는 ECR_Login_and_Image_Push_for_Github_Actions로 적었습니다.

AWS 역할 생성

다음으로는 역할을 생성합니다. IAM > 역할 > 역할 생성으로 이동합니다. 그 후 아래와 같이 필요한 정보를 작성합니다.

 

자격 증명 공급자와 Audience는 Github OIDC provider를 등록했던 것과 똑같이 입력해줍니다.

 

앞서 생성한 정책을 지정해줍니다. 아니면 AmazonEC2ContainerRegistryPowerUser 정책을 선택해도 앞서 필요했던 역할을 포함하고 있으니 이걸 선택해도 됩니다.

 

역할 이름과 설명을 작성하고, 신뢰 정책까지 검토한 다음 역할 생성을 클릭합니다. 저는 알아보기 쉽게 Github@ECR_login_and_push_image로 지정했습니다.

 

여기서 생성한 arn을 복사하여 github repository의 secret 으로 등록해줍시다.

 

그래서 aws 자격증명 step은 아래와 같이 정리가 됩니다. role-seesion-name은 원하는대로 지정해주면 됩니다.

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-region: ap-northeast-2
    role-to-assume: ${{ secrets.ARN_ECR_PUSH_ROLE }}
    role-session-name: ecrPrivatePushRole

 

ECR에 로그인합니다. 로컬에서 도커 허브로 login 하는 것과 같습니다.

- name: Login to Amazon ECR
  id: login-ecr
  uses: aws-actions/amazon-ecr-login@v2

 

변수를 설정합니다. 레지스트리를 찾고, 그 다음 우리가 생성한 레포지터리를 지정한 다음, 이미지 태그를 설정합니다. VERSION은 단순히 SemVer2 버전을 명시한 파일이므로, 원하는 대로 설정해주시면 됩니다. ex) 1.0.0 등

- name: Set var
  id: set-var
  run: |
    echo ::set-output name=ECR_REGISTRY::${{ steps.login-ecr.outputs.registry }}
    echo ::set-output name=ECR_REPOSITORY::nodejs-server
    echo ::set-output name=IMAGE_TAG::$(cat VERSION)

 

도커 이미지를 빌드합니다. set-var step의 결과물들을 사용해 이미지 태그를 작성해줍니다.

- name: Docker image Build
  id: build-image
  run: |
    docker build \
      -f Dockerfile \
      -t ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }} .

 

이미지를 레지스트리로 push합니다.

- name: Docker image Push
  id: push-image
  run: |
    docker push ${{ steps.set-var.outputs.ECR_REGISTRY }}/${{ steps.set-var.outputs.ECR_REPOSITORY }}:${{ steps.set-var.outputs.IMAGE_TAG }}

 


 

이렇게 CI 과정이 대부분 마무리 되었습니다. 여기서 manifest를 수정하는 과정만 추가하면 추가되면 마무리 될 것 같네요.

 

2️⃣ Helm 리소스 등록

https://dev-scratch.tistory.com/177

 

[Kubernetes/helm] 쿠버네티스의 패키지 관리자 Helm 알아보기

헬름은 쿠버네티스를 위한 패키지 관리자다. 헬름을 이용하면 여러 유용한 패키지들을 손쉽게 설치할 수 있다. 예를 들어서, metalLB를 helm로도 설치할 수 있다. Kustomize와 비슷한 격이다. 공식 문

dev-scratch.tistory.com

헬름에 대한 내용은 위 링크를 참고해 주세요~

AppProject 생성

먼저 헬름 앱 프로젝트를 생성합니다. 여기서 생성할 프로젝트에 지정할 레포지터리는 미리 생성해 두어야 합니다.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: nodejs
  namespace: argocd
  # Finalizer that ensures that project is not deleted until it is not referenced by any application
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  description: Project for nodejs

  sourceRepos:
    - 'https://github.com/stae1102/argocd-gitops.git'

  destinations:
    - namespace: '*'
      server: https://kubernetes.default.svc

  clusterResourceWhitelist:
    - group: '*'
      kind: '*'

 

간단한 테스트를 위해 프로젝트 상세 파일을 생성했습니다. 각 항목은 다음과 같습니다.

 

  • metadata.finalizers: 애플리케이션을 삭제할 때 사용할 finalizer. 굳이 지정하지 않아도 되지만, App of Apps 패턴을 사용할 때 계단식으로 삭제할 수 있어 유용합니다.
  • spec.description: 프로젝트 설명
  • spec.sourceRepos: 프로젝트에서 사용할 레포지터리. 리스트 형태로 원하는 레포지터리를 지정합니다. 애스터리스크를 사용해 모든 레포지터리를 지정할 수도 있고, !(느낌표)를 붙여서 레포지터리를 제외할 수도 있습니다.
  • spec.destinations: argocd가 동기화할 클러스터.
  • spec.clusterResourceWhitelist: argocd가 동기화할 클러스터 리소스. 종류에 상관 없이 모두 지정하고자 애스터리스크를 사용했습니다.

쿠버네티스에 적용합니다.

kubectl apply -f 앱프로젝트.yaml

Application 생성

그 다음 실질적으로 사용할 애플리케이션을 생성합니다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nodejs-server
  namespace: argocd
spec:
  destination:
    namespace: default
    server: 'https://kubernetes.default.svc'
  project: nodejs
  source:
    repoURL: https://github.com/stae1102/argocd-gitops.git
    targetRevision: HEAD
    path: nodejs-server
    helm:
      valueFiles:
        - values.yaml
  syncPolicy:
    automated:
      prune: true
    syncOptions:
      - Validate=false
      - CreateNamespace=true
      - PrunePropagationPolicy=background

 

각 설정은 다음과 같습니다.

  • spec.destination: 위와 같습니다.
  • spec.projects: 등록될 프로젝트. 앞서 생성한 프로젝트의 metadata.name과 같이 지정하여 그룹화합니다.
  • spec.source: 애플리케이션이 사용할 소스. 레포지터리 URL, 리비전, 애플리케이션이 담긴 디렉터리 주소, 헬름이 사용할 value 파일 주소까지 작성합니다.
  • spec.syncPolicy: 동기화 정책. 자동으로 동기화를 하려면 automated.prune을 true로 설정합니다.

쿠버네티스에 적용합니다.

kubectl apply -f 애플리케이션.yaml

 

이렇게 모두 완료가 되었으면, 애플리케이션의 helm chart를 만들어줍니다. 저는 배포 레포지터리의 nodejs-server 디렉터리에 차트를 생성했습니다.

.
├── Chart.lock
├── Chart.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── sealed-secret.yaml
│   ├── secrets.yaml # git ignore
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

 

먼저 values.yaml을 확인하겠습니다. 간단하게 배포할 목적이므로 최대한 간결하게 작성했습니다.

# Default values for nodejs-server.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1
imagePullSecrets:
  - name: ecr-regcred
nameOverride: ""
fullnameOverride: ""

image:
  repository: 213360237800.dkr.ecr.ap-northeast-2.amazonaws.com/nodejs-server
  tag: "1.0.0"
  
serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Automatically mount a ServiceAccount's API credentials?
  automount: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  name: ""
  
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000

securityContext: {}
# capabilities:
#   drop:
#   - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000

service:
  type: LoadBalancer
  port: 3000
ingress:
  enabled: false
  className: ""
  annotations: {}
  # kubernetes.io/ingress.class: nginx
  # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
#   cpu: 100m
#   memory: 128Mi
# requests:
#   cpu: 100m
#   memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
#   secret:
#     secretName: mysecret
#     optional: false

# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
#   mountPath: "/etc/foo"
#   readOnly: true

nodeSelector: {}
tolerations: []
affinity: {}

 

중요한 내용은 다음과 같습니다.

replicaCount: 1
imagePullSecrets:
  - name: ecr-regcred

image:
  repository: 213360237800.dkr.ecr.ap-northeast-2.amazonaws.com/nodejs-server
  tag: "1.0.0"

 

repository는 ecr 레포지터리를 지정해줍니다. tag는 manifest를 수정하면서 같이 수정되므로 크게 신경쓸 필요 없습니다.

 

여기서 레지스트리가 private이라면, 그에 상응하는 권한을 가지고 있어야 합니다. 그렇기 때문에 imagePullSecrets에 secret을 지정해주도록 합니다.

kubectl create secret docker-registry ecr-regcred \
  --dry-run=client \
  --docker-server="$ECR_REGISTRY_URI" \
  --docker-username=AWS \
  --docker-password="$(aws ecr get-login-password --region ap-northeast-2)" \
  --namespace=default \
  -o yaml | kubectl apply -f -

 

시크릿을 생성한 다음 쿠버네티스에 적용하는 코드입니다. 이는 로컬 터미널에서 수행될 예정이며, aws cli에서 ecr에 로그인하고 pull할 권한이 있는 유저로 설정이 되어 있어야 합니다.

 

ECR 레지스트리 URI를 외부에서 받아오기 위해 스크립트를 작성했습니다.

#!/bin/bash

ECR_REPOSITORY_NAME=""

while getopts "r:" opt; do
  case $opt in
    r)
      ECR_REPOSITORY_NAME="$OPTARG"
      ;;
    \?)
      echo "잘못된 옵션: -$OPTARG" >&2
      exit 1
      ;;
  esac
done

if [ -z "$ECR_REPOSITORY_NAME" ]; then
  echo "ECR_REGISTRY_NAME이 지정되지 않았습니다." >&2
  exit 1
fi

ECR_REGISTRY_URI=$(aws ecr describe-repositories --repository-names "$ECR_REPOSITORY_NAME" --query 'repositories[0].repositoryUri' --output text)

if [ -z "$ECR_REGISTRY_URI" ]; then
  echo "ECR 레지스트리 URI를 가져올 수 없습니다." >&2
  exit 1
fi

kubectl create secret docker-registry ecr-regcred \
  --dry-run=client \
  --docker-server="$ECR_REGISTRY_URI" \
  --docker-username=AWS \
  --docker-password="$(aws ecr get-login-password --region ap-northeast-2)" \
  --namespace=default \
  -o yaml | kubectl apply -f -

 

레포지터리 이름을 받아서 registry uri를 가져오는 스크립트입니다. 이제 이미지를 받아올 때, 해당 시크릿을 통해 프라이빗 레지스트리에 접근할 수 있게 됐습니다.

 

./add_registry_secret.sh -r 레포지터리_이름

Sealed-Secret 생성

애플리케이션을 배포할 때, 환경변수를 사용하여 비밀번호나 토큰의 시크릿값, SSO 설정 등의 민감한 정보를 가져옵니다. 이때, 주로 사용되는 것이 ConfigMap과 Secret입니다. ConfigMap보다는 Secret이 조금 더 안전한 오브젝트인데, 여전히 Secret은 base64로 인코딩이 되므로 레포지터리에 그대로 올릴 수 없습니다.

 

그래서 이 문제를 해결하고자 SealedSecret이 등장했습니다.

출처: https://docs.bitnami.com/tutorials/sealed-secrets
출처: https://github.com/bitnami-labs/sealed-secrets

 

sealed-secret 차트에서 확인했을 때, 이렇게 설명이 되어 있네요. 즉, Secret을 제외한 모든 쿠버네티스 설정을 git으로 관리하고자 할 때 사용할 수 있는 솔루션입니다. 시크릿을 SealedSecret으로 암호화하여 레포지터리에 안전하게 올릴 수 있으며, 이 시크릿은 클러스터에서 동작하는 컨트롤러만 복호화할 수 있습니다.

 

SealedSecret은 helm chart를 통해 쉽게 설치할 수 있습니다.

helm repo add sealed-secret https://bitnami-labs.github.io/sealed-secrets/
"sealed-secret" has been added to your repositories
$ helm search repo sealed
NAME                        	CHART VERSION	APP VERSION	DESCRIPTION
bitnami/sealed-secrets      	1.5.12       	0.24.3     	Sealed Secrets are "one-way" encrypted K8s Secr...
sealed-secret/sealed-secrets	2.13.2       	v0.24.3    	Helm chart for the sealed-secrets controller.
$ helm install sealed-secret sealed-secret/sealed-secrets --namespace=kube-system
NAME: sealed-secret
LAST DEPLOYED: Mon Nov 13 22:12:26 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **

You should now be able to create sealed secrets.

1. Install the client-side tool (kubeseal) as explained in the docs below:

    https://github.com/bitnami-labs/sealed-secrets#installation-from-source

2. Create a sealed secret file running the command below:

    kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \
    kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
      --format yaml > mysealedsecret.[json|yaml]

The file mysealedsecret.[json|yaml] is a commitable file.

If you would rather not need access to the cluster to generate the sealed secret you can run:

    kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
      --fetch-cert > mycert.pem

to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g.

    kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \
    kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
      --format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml]

3. Apply the sealed secret

    kubectl create -f mysealedsecret.[json|yaml]

Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret.

Both the SealedSecret and generated Secret must have the same name and namespace.

 

이렇게 SealedSecret을 설치해준 다음 kubeseal로 SealedSecret 상세 파일을 생성할 수 있습니다. 해당 차트에서도 추천하는 방법입니다.

 

아래 링크에서 kubeseal 설치방법을 확인하여 설치해줍니다.

https://github.com/bitnami-labs/sealed-secrets#kubeseal

 

GitHub - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets

A Kubernetes controller and tool for one-way encrypted Secrets - GitHub - bitnami-labs/sealed-secrets: A Kubernetes controller and tool for one-way encrypted Secrets

github.com

이제 아래 명령어를 입력해 줍니다. 여기서 controller-name은 sealed-secret을 설치할 때 사용한 릴리즈 이름입니다. secrets.yaml은 암호화할 시크릿입니다. sealed-secert.yaml은 암호화된 시크릿입니다. 둘 다 원하는 대로 설정해주시면 됩니다.

kubeseal \
      --controller-name=sealed-secret-sealed-secrets \
      --controller-namespace=kube-system \
	  --format yaml < secrets.yaml > sealed-secret.yaml

 

그러면 아래와 같이 SealedSecret이 생성됩니다. base64로 인코딩했을 때보다 확실히 더 복잡해졌네요.

---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: mysql-secret
  namespace: default
spec:
  encryptedData:
    DB_HOST: AgCaTFN9AEH/igbDpH7oeVW+G+Ia8JAMDCdQ4lqNDaK9drVvBMJzEMRe5TttKgU0cV8nKqW7+dgE4Cpc24yV8jiiTHAQmdWToBPPqL4exd3yHyjRxFjd9mhcbAbkaoD5mgMdiSg/gX7aX+uOPmWJSEXU4mlTOeNgVlc3XjZ/my/MjcdrVmw8Xh8vKUgqZfGtFSiLuLeYJxN8WZSzx37lzqHz7pH+av26BqXiwkee/5zxcnqEGxM8O08Khw0/M5gLIx7Dz2chVfkxnmEgUYZZ5uqsckaRwlhVFeH4TH9Aw2yQNCy1CM6xCxd5z6CP9m8VyXxWa2fAh3kFsy05rIk4C8YT70YlvNmut5dhVCQmMYQZ0PT37xdGXiqWcKTwEBfUNuUl6WniXvXEYh6nGJs+1wi+1COft2KzxZDPTTN35S/Jlh6JDaMjMBhmiyfXue5JWf7O2xLM4ChLd58NniI6hl7GV1yZqNWK4e3cvme5866YuoTrxnDb+DHG/wlOt839yadjV8EFyst+mXi8Yv11EIQK9ndTTULzqzWpK2s51cubB5xrnLK2lP+R7wEBCIfsurtNalPQojlh27yns+URvK1OhMzFwdylf6YXOCoD853AGJn0LOGWNKKkD4aHTfq1GZtUcm74ZtkLkuz8iAT86fVJPvx8+GyAPDWybmf8xwbuwAHbg5EUzn5AV3EN3FaUchJKPuUWlrT2zQ==
    DB_NAME: AgAH3Udxzg3IIqwcJsBMBzNi/8Mud2XgLBxB0zyKQIq+atYdBjFtbBFt6yMcDzNrL/iMqsS6L7E5Dh7f1UiHmpnOPPat1Mbs/47yfEe7Ua2OKhZ/r2aerMFA8E3hSY4tonB0ZtuQSdr+v7jZjpOsMVRfJwYZWGOaUhgZpl2G0FAWfDB27ycFrzDQmbMaRRBqnxfc1OLHROT5Etj3xf9jBbFqrZa7VsgJ2aOGgpXDdCbfuYhhWom9gNZRP3I8TVBns7Ka7QiO04RcYJK9ZaoRVb5frO+G4oTk8t7Fzk3lv+7nLzvfybmWzTGYr1KtWNeX4CxYFrJvauzlj2mmrvWZA3LtOO/sC/LRIXZNqE7O1kCJPuMlFs+EqrceVCVdCAycgopKE7iNwsDTGaNobdz/frvrbhLq/xxiElDnMAam1aMJORII4FeKfuzLBc47JRSQy9MeFpotP3BebxH/DjRJJaxPxRtDBwW3V/ULRRNcu5bcK+e0ffuU7QkAquhGHzFrf+CO1cV2O5YD746QIhkrUg+fy3wPblSo5/Ik07tbsSvALFxLXUVJzEkFsxoIFbUofI6x2TdyKf9fNNpx2LBvP/HzBSxwWLgaM4bemhH6M/mLYPaHwPIUZvEb1egMiW0jSGmWOt0K9IXVL1XveVqi9hubZXpnDv6emwy53fGUePvK89mAe0N0RyphJI1PZFP99RJOKg3OKEg=
    DB_PASSWORD: AgBI/eFBJ14W0XkM4MDbuqqvLEH5fd7GazbTsBy8EDN3OpkJV1IITOGfKVudLlTVUUYDA2hV0cejwXPyy3yCXuWGPXjDnSXb0k+qsqe40f/UE2fs3+xWzDdNC9kz10BQfMYhwlOWax9zaU/4o0mxq3C/Cw1HVmWnRSE44GvSYj5PwQI6/aM66pgl2DXLoscO+RWNru34tMWItre2/crAn5Vgx0Jc8CgZDyCENZgM2C4sOzU6IxIT0bISPVsUhD/e7KyGhko51nIxWBSgRFf0h+SHpAzQap1nIDisqVc8Q1DPwdL+lhwF4Tcrd1KapiGQDXKmmnl4SZxXU8beAMYNnOHo9nzt2+4zMekYN3Jorq959pL56//yeXwrHsxN6sC9qYhX7yYjbxFDF4/M50JWB7GrIRGudwMfEsxKLkKjHDc95YB+VDP8TV3Gm4JITcBb7X4j/qhUwUjCyxcpbiTQrdnHgUCbSxIt8QXd68dv07NMZXIsAwUFLx5/oZB1aM9AGNRwySfZkI7BmyPdf6T3F4Q8V4hsHTA/IPj4jVLE/XTWGr+cnOHt8KSNShjQAJO+eE+RpCNY1yquFWznuG1MeSrrIW8x2eE7+M8SEGm5Fp4ug5yBjdBNzW4QAcVT87jk8Yw29EOWIxdipQuCRPSkBMa3VT7ZCW6cMgFvI0KwCWG+1WPkfxeNVJm7fm7IMN4P2a6rnxgYAKxiK0BPqOO4
    DB_PORT: AgCNTtfaW1r0E6oVTCCFPoSaTZkSYSDFwyILft36DlFsFpneVZjIvf3LEtTEVuPsZvq4J0fpjiXLAhJgoFlqUsDa/3wwmM+CwZ1H9BinpPFj2IOF14F6IAdj0UbStqTYCRc0BVqeMfTRJnJ39SC0GbjY5uGOn8KN9qXw8BvQwfrllZSBuiJd+IO6Sm8dMQKBWUkcbK62cGd1N9X/XLz6qnjizrh6FJxFX7D1LLqi1nCFc6ub1P76zOcP+9RemABI1nj6lu55IVtvpMh0QlSjBtr952nrDtzgub6UGWSduHUkp/z/KtM4WhmIjgHNd2KfSU2xtC4TFBaf1IXfoRsMJTlLpPQ+w5jZgKnTLzcaVolt1TzY8YgdAD+hdJiozRDRDOV8z1DXbYc2ZR7ci8zf+qsfwHQ4myTp+NllYkMBUJJV9oQx6ztrkfE1NAll6Oaz/EQ4sUUD3LQ+4cRjpzY4HVig0gejrS28dN5IRqWwm/G2tNdTrLLcOaySaI3bvvdmLBwY9pXRLa/OekYlOGCqzQpKixpm5N2IqjI2IFAmfUs0YWQL21IMsJL85vJ8Oar4uzuMz1Q4Unf4PWKSGjE4PQ4oK4mdL4pqKtlpBqaj0rSa52GbuY68oEADtysOIx4d0RUXuNJYE9+IYIpFhJMPK6k77PO8PTujAkZ/WMLV8jcmnErGJX1npaQh6/I6zMCYz9QMRbzu
    DB_USERNAME: AgDLyTHrQL0WUWFkVHYtolWr0TEv4+nl/82gb5XKhLSypqbFlfNCkFMg6tUlSH5dscQFIWroX5cKZh3Gp8tjSLpJTZoCK+6Tzb6ZSXmglmp1dkaaWBeqqL3ITJ3IvAEHaYv2nTFEyeNUcHS0utxm9XTw/a9nDAfz3m5Qi90xAVIhXKX8EyNsxX+y2ln5RnkubAo5RM6KzklXLngGhVcKX9zJWrOIWVHuu7haWsxqqtqn7SYNDP+NKlEEP5qOaJumCwc6W7yMLrYvHio0pmy/QUfD2YVoqCoGaUCKGZdQbc/4ZE08evYpvGI7l1HCLfuE+cj9XTn2GFZxbeh48uqFi+Sz+oYZahCCHT2XBqfTFy0s7ncZpCX9/GbV+0MhwHxzQiJsvrb+VR6k63k/Eb+xVQlSO1J8B2ncZSUi/C/eawO0m4YFmcTC0HmDylygOdWbGqLE6fvJI+SNTbE+rXSDrCtG/0/ARj/bFCr2WdDVErPjJ7ZjOqILGdyoCnh/tj+M1LQByCp/JZdA8loMC7B51fu0QK7jkLwsg1MzjPqkoEeWiHH1LkJYHzpgoSptRrP3HoJ96Laq6zgl/24I4cLZhS1A4u5N2RqpIjNufkM3GYVRBqYNoYofBe+3dMeUicgFeU/ub8r1L9X/ktx56gAdnZG6iyi1m62JwhihmnaDOYwOgXvJiVcKqqTMTVfI+nw+KtOTtYvzHWDxWb0=
  template:
    metadata:
      creationTimestamp: null
      name: mysql-secret
      namespace: default
    type: Opaque

애플리케이션에서 SealedSecret 사용

디플로이먼트에서 SealedSecret을 사용할 수 있습니다.

...
containers:
  - name: {{ .Chart.Name }}
    securityContext:
      {{- toYaml .Values.securityContext | nindent 12 }}
    image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
    imagePullPolicy: {{ .Values.image.pullPolicy }}
    ports:
      - name: http
        containerPort: {{ .Values.service.port }}
        protocol: TCP
    livenessProbe:
      httpGet:
        path: /
        port: http
    readinessProbe:
      httpGet:
        path: /
        port: http
    resources:
      {{- toYaml .Values.resources | nindent 12 }}
    {{- with .Values.volumeMounts }}
    volumeMounts:
      {{- toYaml . | nindent 12 }}
    {{- end }}
    envFrom:
      - secretRef:
          name: mysql-secret
...

 

envFrom 필드에 secretRef name 값으로 SealedSecret name을 지정해주면 끝입니다.

 

3️⃣ github actions workflow 작성 - CD

CI 이후 사용할 job입니다.

cd:
  needs: [ ci ]
  runs-on: ubuntu-latest
  steps:
    - name: Checkout Private Repository
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        repository: stae1102/argocd-gitops
        ref: main
        token: ${{ secrets.PAT }}

    - name: Replace image tag in helm values (LOCAL)
      uses: mikefarah/yq@master
      env:
        IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
      with:
        cmd: yq eval -i '.image.tag = env(IMAGE_TAG)' 'nodejs-server/values.yaml'

    - name: Commit helm chart changes
      env:
        IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
      run: |
        cd nodejs-server
        git config --global user.email "stae1102@gmail.com"
        git config --global user.name "stae1102"

        git add values.yaml
        git commit --message "ci: update nodejs-server image tag to $IMAGE_TAG"

    - name: Push commit
      uses: ad-m/github-push-action@master
      with:
        github_token: ${{ secrets.PAT }}
        repository: stae1102/argocd-gitops

 

각 step은 다음과 같습니다.

- name: Checkout Private Repository
  uses: actions/checkout@v4
  with:
    fetch-depth: 0
    repository: stae1102/argocd-gitops
    ref: main
    token: ${{ secrets.PAT }}

 

다른 레포지터리에 checkout 하기 위해 토큰을 사용합니다. 토큰에는 github public access token이 사용됩니다.

 

생성한 token은 github repository의 secret으로 등록해줍니다.

- name: Replace image tag in helm values (LOCAL)
  uses: mikefarah/yq@master
  env:
    IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
  with:
    cmd: yq eval -i '.image.tag = env(IMAGE_TAG)' 'nodejs-server/values.yaml'

 

이미지 태그를 수정합니다. 경로는 내가 바꿀 helm chart의 values.yaml 경로를 지정해주어야 합니다. 저의 경우, nodejs-server 차트의 values.yaml로 지정했습니다.

- name: Commit helm chart changes
  env:
    IMAGE_TAG: ${{ needs.ci.outputs.IMAGE_TAG }}
  run: |
    cd nodejs-server
    git config --global user.email "stae1102@gmail.com"
    git config --global user.name "stae1102"

    git add values.yaml
    git commit --message "ci: update nodejs-server image tag to $IMAGE_TAG"

 

헬름 차트 변경 사항을 커밋합니다.

- name: Push commit
  uses: ad-m/github-push-action@master
  with:
    github_token: ${{ secrets.PAT }}
    repository: stae1102/argocd-gitops

 

커밋된 내용을 푸시합니다. 여기도 마찬가지로 token을 지정해줍니다. 아니면, Commit helm chart changes 스텝에서 git push까지 해줘도 됩니다.

 

이제 애플리케이션 레포지터리에 작업을 완료한 후 작업을 반영시키면 완료입니다.

정리하며

깃허브 액션의 초록불은 언제봐도 절 설레게 합니다. 완벽한 파이프라인 구현은 아니겠지만, 대강 이런 식으로 애플리케이션이 배포가 되는 것을 확인했으며, 행동으로 실천해보니 아주 뿌듯합니다.

 

다음에는 ci 툴을 바꿔보고 파이프라인을 구성해보고자 합니다.

참고

https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions#oidc

 

"Configure AWS Credentials" Action for GitHub Actions - GitHub Marketplace

Configures AWS credentials for use in subsequent steps in a GitHub Action workflow

github.com

https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws

 

Configuring OpenID Connect in Amazon Web Services - GitHub Docs

Use OpenID Connect within your workflows to authenticate with Amazon Web Services.

docs.github.com

https://github.com/marketplace/actions/amazon-ecr-login-action-for-github-actions#permissions

 

Amazon ECR "Login" Action for GitHub Actions - GitHub Marketplace

Logs in the local Docker client to one or more Amazon ECR Private registries or an Amazon ECR Public registry

github.com

https://thingsflow.com/blog/143

 

thingsflow

Creator economy with AI tech

thingsflow.com

https://medium.com/@jerome.decoster/argocd-image-updater-56cd94651393

 

ArgoCD + Image Updater

Playing with ArgoCD Image Updater and public and private repositories.

medium.com

https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#managing-projects

 

Projects - Argo CD - Declarative GitOps CD for Kubernetes

Projects Projects Projects provide a logical grouping of applications, which is useful when Argo CD is used by multiple teams. Projects provide the following features: restrict what may be deployed (trusted Git source repositories) restrict where apps may

argo-cd.readthedocs.io

https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/

 

App Deletion - Argo CD - Declarative GitOps CD for Kubernetes

App Deletion Apps can be deleted with or without a cascade option. A cascade delete, deletes both the app and its resources, rather than only the app. Deletion Using argocd To perform a non-cascade delete: argocd app delete APPNAME --cascade=false To perfo

argo-cd.readthedocs.io

https://levelup.gitconnected.com/from-local-development-to-kubernetes-cluster-helm-https-ci-cd-gitops-kustomize-argocd-5d9de6f5364c

 

From local development to Kubernetes — Cluster, Helm, HTTPS, CI/CD, GitOps, Kustomize, ArgoCD —…

This is the second part of the series: From local development to Kubernetes — Cluster, Helm, HTTPS, CI/CD, GitOps, Kustomize, ArgoCD. You…

levelup.gitconnected.com

https://medium.com/@seifeddinerajhi/gitops-ci-cd-automation-workflow-using-github-actions-argocd-and-helm-charts-deployed-on-k8s-3811b253030b

 

GitOps: CI/CD automation workflow using GitHub Actions, ArgoCD, and Helm charts deployed on K8s…

Introduction

medium.com