AWS Cloud School 13기 89일차

Forever 김·2026년 5월 7일

AWS Cloud School

목록 보기
79/97

TIL

오늘은 어제 배운 ArgoCD에 GitHub Actions를 붙여서 진짜 의미의 CI/CD 파이프라인을 완성했다. 코드를 push하면 자동으로 이미지가 빌드되고, ECR에 올라가고, 매니페스트가 수정되고, ArgoCD가 EKS에 배포하는 흐름을 직접 구성해봤다. 수동으로 하던 1~4단계가 전부 자동화되는 걸 보니까 CI/CD가 왜 필요한지 몸으로 느껴졌다.


CI/CD 전체 흐름 복습

어제는 ArgoCD로 CD 파트를 다뤘다면, 오늘은 CI 파트인 GitHub Actions를 연결해서 파이프라인을 완성했다.

전체 흐름은 이렇다.

소스코드 push (GitHub)
  → GitHub Actions (CI)
    → 코드 체크아웃
    → Docker 이미지 빌드
    → ECR push
    → 매니페스트 레포의 이미지 태그 자동 수정 & push
  → ArgoCD (CD)
    → 매니페스트 변경 감지
    → kubectl apply → EKS 자동 배포

대표적인 CI 툴로는 Jenkins(오픈 소스, 가장 널리 쓰임), GitHub Actions, AWS CodeBuild, GitLab Runner 등이 있다. CD 툴은 k8s 환경에서는 ArgoCD, AWS 환경에서는 CodeDeploy가 대표적이다. GitOps는 GitHub을 사용하는 DevOps 방식이라고 이해하면 된다.


레포지토리 분리 전략

핵심은 레포지토리를 두 개로 분리한다는 점이다.

레포역할
소스코드 레포 (fastapi)앱 코드 + GitHub Actions workflow
매니페스트 레포 (app2)K8s 매니페스트 파일 (ArgoCD가 모니터링)

왜 분리하냐면, 소스코드 레포에 매니페스트까지 같이 두면 GitHub Actions가 이미지 태그를 수정해서 push할 때 또 Actions가 트리거되는 무한 루프가 생기기 때문이다.

분리하면 이런 장점도 생긴다.

  • 배포 이력(매니페스트 변경 이력)과 개발 이력(소스코드 커밋)이 명확히 분리됨
  • 여러 서비스의 매니페스트를 하나의 레포에서 관리 가능
  • 배포 권한과 개발 권한을 별도로 제어 가능

실습 — 소스코드 레포 구성

기존에 sfapp 디렉토리 전체가 하나의 git 레포였는데, 이걸 분리해야 한다.

root@eks:~/sfapp# ls -al
# fastapi / svelte 두 디렉토리로 구성

root@eks:~/sfapp# rm -rf .git
# 기존 git 레포 제거

root@eks:~/sfapp# cd fastapi
root@eks:~/sfapp/fastapi# rm -rf local-fastapi.yml
# 소스코드 레포에는 매니페스트 불필요 → 삭제

소스코드 레포 초기화 및 GitHub push:

root@eks:~/sfapp/fastapi# git init
root@eks:~/sfapp/fastapi# git remote add origin https://github.com/<본인계정>/fastapi.git
root@eks:~/sfapp/fastapi# git branch -M main
root@eks:~/sfapp/fastapi# git add .
root@eks:~/sfapp/fastapi# git commit -m "first"
root@eks:~/sfapp/fastapi# git push origin main

수동 업데이트 흐름 이해

GitHub Actions를 붙이기 전에 수동으로 앱을 업데이트하는 흐름을 먼저 이해해두자.

# 1. 소스코드 변경 후 git push
echo test > update.txt
git add . && git commit -m "updated app fast:2" && git push origin main

# 2. 이미지 빌드
docker build -t $ECR/fast:2 .

# 3. ECR push
docker push $ECR/fast:2

# 4. 매니페스트 파일 수정 (fast:1 → fast:2)
# app2 레포의 fastapi/fastapi.yml 에서 이미지 태그 직접 수정

# 5. ArgoCD가 변경 감지 → 자동 배포

이 1~4 과정을 자동화하는 게 GitHub Actions다.


GitHub Actions Workflow 작성

workflow는 .github/workflows/ 경로에 yaml 파일로 작성한다.

트리거 설정

name: fast CI/CD

on:
  push:
    branches: [main]       # main 브랜치에 push될 때 트리거
    paths-ignore:
      - '.gitignore'
      - '.dockerignore'    # 이 파일들의 변경은 무시

실행 환경 및 소스코드 체크아웃

jobs:
  ci:
    runs-on: ubuntu-latest   # GitHub Actions가 제공하는 Ubuntu VM에서 실행
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 1     # 가장 최근 커밋 히스토리만 가져옴 (속도 최적화)

runs-on은 Actions가 동작할 환경이다. 소스코드를 클론하고 이미지를 빌드하려면 어떤 실행 환경이 필요한데, GitHub Actions가 제공하는 ubuntu-latest VM을 쓴다고 보면 된다.

uses는 일종의 라이브러리/모듈이다. 현재 스텝에서 소스코드를 clone해오는 역할을 한다.

AWS 자격증명 및 ECR 로그인

      - name: Configure AWS credentials
        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: ap-northeast-2

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

aws configure와 비슷한 역할이다. secrets.XXX로 참조하는 값들은 GitHub 레포 → Settings → Secrets and variables → Actions에서 등록한다.

환경변수 설정

      - name: Set Variables
        id: set-var
        run: |
          echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV
          echo "ECR_REPOSITORY=fast" >> $GITHUB_ENV
          echo "IMAGE_TAG=${{ github.run_number }}" >> $GITHUB_ENV
          echo "GIT_EMAIL=your@email.com" >> $GITHUB_ENV
          echo "GIT_NAME=yourgithubname" >> $GITHUB_ENV

GITHUB_ENV 파일에 저장해두면 이후 스텝에서 ${{ env.변수명 }}으로 호출할 수 있다.

github.run_number를 이미지 태그로 쓰는 이유가 중요하다. latest 같은 고정 태그를 쓰면 ArgoCD가 매니페스트 변경을 감지하지 못한다. run_number는 Actions가 실행될 때마다 1씩 증가하는 값이라, 매번 태그가 달라지면서 ArgoCD가 변경을 인식하고 자동 배포를 트리거한다.

이미지 빌드 & ECR push

      - name: Docker Image Build
        run: |
          docker build -t ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} .

      - name: Docker image Push
        run: |
          docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}

매니페스트 레포 체크아웃 & 이미지 태그 수정 (핵심)

      # 소스코드 레포 → 매니페스트 레포로 전환
      - name: Checkout Deployment Repository
        uses: actions/checkout@v4
        with:
          repository: <본인계정>/app2   # 매니페스트 레포
          ref: main
          token: ${{ secrets.GH_TOKEN }}

      # sed로 이미지 태그 자동 수정
      - name: k8s manifest update
        run: |
          sed -i "s@ \
          image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:.*@ \
          image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}@g" ./fastapi/fastapi.yml

sed -i s@(바뀌기전)@(바뀐후)@g 파일이름 형태다. 경로에 /가 포함되어 있어서 구분자를 @로 바꿔서 쓴다.

수정된 매니페스트 push

      - name: Commit and Push
        run: |
          git config user.email ${{ env.GIT_EMAIL }}
          git config user.name ${{ env.GIT_NAME }}
          git add ./fastapi/fastapi.yml
          git commit -m "Update image tag"
          git push origin main

이 push가 ArgoCD의 트리거가 된다. ArgoCD가 매니페스트 레포의 변경을 감지하고 자동으로 kubectl apply를 실행한다.

전체 workflow 파일

name: fast CI/CD

on:
  push:
    branches: [main]
    paths-ignore:
      - '.gitignore'
      - '.dockerignore'

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 1
      - name: Configure AWS credentials
        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: ap-northeast-2
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2
      - name: Set Variables
        id: set-var
        run: |
          echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV
          echo "ECR_REPOSITORY=fast" >> $GITHUB_ENV
          echo "IMAGE_TAG=${{ github.run_number }}" >> $GITHUB_ENV
          echo "GIT_EMAIL=your@email.com" >> $GITHUB_ENV
          echo "GIT_NAME=yourgithubname" >> $GITHUB_ENV
      - name: Docker Image Build
        run: |
          docker build -t ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} .
      - name: Docker image Push
        run: |
          docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
      - name: Checkout Deployment Repository
        uses: actions/checkout@v4
        with:
          repository: <본인계정>/app2
          ref: main
          token: ${{ secrets.GH_TOKEN }}
      - name: k8s manifest update
        run: |
          sed -i "s@ \
          image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:.*@ \
          image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}@g" ./fastapi/fastapi.yml
      - name: Commit and Push
        run: |
          git config user.email ${{ env.GIT_EMAIL }}
          git config user.name ${{ env.GIT_NAME }}
          git add ./fastapi/fastapi.yml
          git commit -m "Update image tag"
          git push origin main

GitHub Secrets 설정

workflow에서 ${{ secrets.XXX }}로 참조하는 값들은 GitHub 레포 → Settings → Secrets and variables → Actions에서 등록한다.

Secret 이름
AWS_ACCESS_KEY_IDIAM 액세스 키
AWS_SECRET_ACCESS_KEYIAM 시크릿 키
GH_TOKENGitHub Personal Access Token (repo 권한)

총 3개의 시크릿을 구성해야 한다. 액세스 키는 ECR 접근 권한만 있는 전용 키를 쓰는 게 베스트다. 지금은 실습이라 관리자 권한을 썼지만 운영 환경에서는 최소 권한 원칙을 지켜야 한다. GitHub 토큰은 매니페스트 레포(app2)에 push하기 위해 쓰인다.


실습 2 — Svelte CI/CD 구성 + ALB Ingress 연동

FastAPI에 이어 Svelte 프론트엔드도 같은 방식으로 CI/CD를 구성하고, ALB Ingress를 통해 외부에 노출하는 실습이다.

FastAPI 매니페스트 (ALB Ingress 포함)

# 1. FastAPI Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep-fast
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fast
  template:
    metadata:
      labels:
        app: fast
    spec:
      containers:
      - name: con-fast
        image: <ECR계정ID>.dkr.ecr.ap-northeast-2.amazonaws.com/fast:3
        ports:
          - containerPort: 8000

---
# 2. FastAPI Service (ClusterIP — 내부망만 노출)
apiVersion: v1
kind: Service
metadata:
  name: svc-fast
spec:
  type: ClusterIP
  selector:
    app: fast
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 8000

---
# 3. ALB Ingress (외부 접속 입구)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ing-fast
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: svc-fast
                port:
                  number: 8000

ALB Controller 설치 절차

ALB Ingress를 쓰려면 클러스터에 AWS Load Balancer Controller가 설치되어 있어야 한다.

1단계: 환경변수 설정

export CLUSTER_NAME=myc
export ACCOUNT_ID=<본인계정ID>
export VPC_ID=<VPC ID>
export REGION=ap-northeast-2

2단계: OIDC 공급자 및 IAM 서비스 어카운트 생성

EKS 클러스터 내부의 파드가 AWS 권한을 사용할 수 있도록 연결한다.

# OIDC 공급자 연결
eksctl utils associate-iam-oidc-provider --cluster $CLUSTER_NAME --approve

# IAM 서비스 어카운트 생성 및 정책 연결
eksctl create iamserviceaccount \
  --cluster=$CLUSTER_NAME \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --role-name AmazonEKSLoadBalancerControllerRole \
  --attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve

# 확인
kubectl get sa -n kube-system

3단계: Helm으로 AWS Load Balancer Controller 설치

# Helm 레포지토리 추가 및 업데이트
helm repo add eks https://aws.github.io/eks-charts
helm repo update eks

# 컨트롤러 설치
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=myc \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

# 확인
kubectl get pods -n kube-system | grep aws-load-balancer-controller

4단계: 매니페스트 배포 및 ALB 주소 확인

kubectl apply -f fastapi.yml

# 생성된 ALB DNS 주소 확인
kubectl get ingress

소스코드 레포와 매니페스트 레포를 분리하는 이유

처음엔 "그냥 한 레포에 다 넣으면 안 되나?" 싶었는데, 직접 해보니까 이유가 명확해졌다.

소스코드 레포에 매니페스트를 같이 두면:
1. 소스코드 push → Actions 트리거
2. Actions가 매니페스트 이미지 태그 수정 후 같은 레포에 push
3. 그 push가 또 Actions를 트리거 → 무한 루프

분리하면 이 문제가 없고, 추가로 이런 장점도 생긴다.

  • 배포 이력과 개발 이력이 명확히 분리됨
  • 여러 서비스의 매니페스트를 하나의 레포에서 관리 가능
  • 배포 권한과 개발 권한을 별도로 제어 가능

정리

오늘 핵심은 "코드 push 한 번으로 EKS 배포까지 자동화" 다. GitHub Actions가 CI를 담당하고, ArgoCD가 CD를 담당하는 역할 분리가 깔끔하다. 특히 이미지 태그를 run_number로 관리하는 부분이 ArgoCD와 연동하는 핵심 포인트였다.

다음 단계는 Svelte 프론트엔드도 같은 방식으로 CI/CD를 구성해서 FastAPI 백엔드와 연동하는 것이다. 소스코드 레포는 따로, 매니페스트는 같은 배포 레포에 디렉토리만 나눠서 관리하면 된다.

이번 velog는 AWS Kiro를 통해 작성하였다.

profile
나를 한줄로

0개의 댓글