GitHub Actions Advanced Workflows: Docker, Kubernetes CI/CD
GitHub Actions를 활용한 Docker, Kubernetes 기반 CI/CD 파이프라인 구축 심화 가이드. DevOps 엔지니어링 관점에서 자동화 전략을 탐구합니다.
GitHub Actions Advanced Workflows: Docker, Kubernetes CI/CD
현대 소프트웨어 개발에서 CI/CD(Continuous Integration/Continuous Deployment)는 더 이상 선택이 아닌 필수 요소로 자리 잡았습니다. 특히 클라우드 네이티브 환경에서 Docker 컨테이너와 Kubernetes 오케스트레이션은 핵심 기술로 활용되며, 이들의 효율적인 관리와 배포는 DevOps 엔지니어링의 중요한 과제입니다. GitHub Actions는 이러한 복잡한 프로세스를 자동화하고, 개발 워크플로우를 혁신하는 강력한 도구입니다.
이 글에서는 GitHub Actions의 기본을 넘어, Docker 이미지 빌드, Kubernetes 배포, 그리고 인프라스트럭처 프로비저닝까지 아우르는 고급 워크플로우 구축 전략을 심층적으로 다룹니다. 실제 CLI 명령어와 YAML 설정 파일 예시를 통해, 여러분의 CI/CD 파이프라인을 더욱 견고하고 효율적으로 만드는 데 필요한 지식과 통찰을 제공하고자 합니다.
GitHub Actions 기본 개념 재정립
GitHub Actions는 GitHub 저장소에서 직접 소프트웨어 개발 워크플로우를 자동화할 수 있게 해주는 CI/CD 플랫폼입니다. .github/workflows 디렉터리 아래에 YAML 파일을 생성하여 워크플로우를 정의하며, 다양한 이벤트(푸시, 풀 리퀘스트 생성 등)에 의해 트리거될 수 있습니다.
핵심 구성 요소는 다음과 같습니다.
- Workflow: 하나 이상의 Job으로 구성되며, 특정 이벤트에 의해 실행되는 자동화된 프로세스입니다.
- Job: Runner에서 실행되는 일련의 Step들로 구성됩니다. 각 Job은 독립적인 가상 머신 환경에서 실행될 수 있습니다.
- Step: Job 내에서 실행되는 개별 명령 또는 Action입니다.
- Action: 특정 작업을 수행하는 재사용 가능한 코드 블록입니다. GitHub Marketplace에서 다양한 Action을 찾아 사용할 수 있습니다.
이러한 구성 요소들을 조합하여 복잡한 CI/CD 파이프라인을 구축할 수 있으며, 특히 Docker와 Kubernetes 환경에서는 그 유연성이 더욱 빛을 발합니다.
Docker 이미지 빌드 및 컨테이너 레지스트리 푸시 자동화
애플리케이션을 컨테이너화하는 것은 현대 DevOps의 기본입니다. GitHub Actions를 사용하여 Docker 이미지를 자동으로 빌드하고, 컨테이너 레지스트리(예: Docker Hub, GitHub Container Registry)에 푸시하는 워크플로우를 구축할 수 있습니다.
먼저, 간단한 Dockerfile 예시를 살펴보겠습니다.
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]
EXPOSE 3000
이제 이 Dockerfile을 사용하여 이미지를 빌드하고 푸시하는 GitHub Actions 워크플로우를 작성합니다. 이 워크플로우는 main 브랜치에 푸시될 때마다 실행되며, 이미지 태그는 Git 커밋 SHA와 버전 정보를 포함하도록 설정합니다.
# .github/workflows/docker-build-push.yml
name: Build and Push Docker Image
on:
push:
branches:
- main
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
env:
DOCKER_IMAGE_NAME: my-node-app
DOCKER_REGISTRY: ghcr.io/${{ github.repository_owner }} # GitHub Container Registry 사용
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # GHCR에 푸시하기 위한 권한
id-token: write # OIDC를 통한 인증을 위해 필요
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub (Optional)
if: github.event_name != 'pull_request' # PR에서는 푸시하지 않으므로 로그인 불필요
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} # GHCR은 GITHUB_TOKEN으로 인증
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,format=long
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }} # PR에서는 빌드만 하고 푸시하지 않음
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
이 워크플로우는 docker/metadata-action을 사용하여 Git 정보를 기반으로 다양한 태그를 자동으로 생성하고, docker/build-push-action으로 이미지를 빌드 및 푸시합니다. secrets.GITHUB_TOKEN은 GitHub Container Registry에 인증하는 데 사용되며, Docker Hub와 같은 외부 레지스트리에는 별도의 secrets.DOCKER_USERNAME, secrets.DOCKER_PASSWORD를 GitHub Secrets에 등록하여 사용할 수 있습니다.
Kubernetes 기반 애플리케이션 배포 파이프라인 구축
컨테이너 이미지가 레지스트리에 푸시되면, 다음 단계는 이를 Kubernetes 클러스터에 배포하는 것입니다. GitHub Actions는 kubectl CLI 도구와 연동하여 Kubernetes 리소스를 관리할 수 있습니다.
Kubernetes 클러스터에 배포하기 위해서는 클러스터 접근 권한이 필요합니다. 일반적으로 kubeconfig 파일을 GitHub Secrets에 저장하거나, 클라우드 제공업체의 OIDC(OpenID Connect) 기능을 활용하여 임시 자격 증명을 획득하는 방법이 권장됩니다. 여기서는 kubeconfig를 사용하는 예시를 들어보겠습니다. (실제 운영 환경에서는 OIDC 사용을 적극 권장합니다.)
# .github/workflows/kubernetes-deploy.yml
name: Deploy to Kubernetes
on:
push:
branches:
- main
env:
KUBERNETES_CLUSTER_NAME: my-cluster
KUBERNETES_NAMESPACE: default # 배포할 네임스페이스
DOCKER_IMAGE: ghcr.io/${{ github.repository_owner }}/my-node-app:${{ github.sha }} # 이전 단계에서 빌드된 이미지
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # 환경 설정 (승인 프로세스 등)
permissions:
contents: read
id-token: write # OIDC를 위한 권한
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS Credentials (OIDC 예시, EKS 클러스터의 경우)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-eks-role
aws-region: ap-northeast-2
# 이 단계는 EKS 클러스터에 OIDC를 통해 접근하는 경우에 필요합니다.
# 다른 클러스터 타입(GKE, AKS, On-prem)은 해당 클러스터의 인증 방식에 맞게 변경해야 합니다.
- name: Setup Kubeconfig (AWS EKS 예시)
run: |
aws eks update-kubeconfig --name ${{ env.KUBERNETES_CLUSTER_NAME }} --region ap-northeast-2
# GKE, AKS 등 다른 클러스터는 해당 클라우드의 CLI 명령어를 사용합니다.
# 또는 ${{ secrets.KUBECONFIG_BASE64 }}를 디코딩하여 파일로 저장할 수 있습니다.
# echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config
- name: Deploy to Kubernetes
run: |
kubectl config use-context arn:aws:eks:ap-northeast-2:123456789012:cluster/${{ env.KUBERNETES_CLUSTER_NAME }}
# Kustomize 또는 Helm을 사용하는 것이 일반적입니다.
# 여기서는 간단한 kubectl apply 예시를 보여줍니다.
# 배포할 YAML 파일을 동적으로 업데이트할 수 있습니다.
# 예: sed -i "s|__IMAGE_TAG__|${{ env.DOCKER_IMAGE }}|g" k8s/deployment.yaml
kubectl apply -f k8s/deployment.yaml -n ${{ env.KUBERNETES_NAMESPACE }}
kubectl rollout status deployment/my-node-app -n ${{ env.KUBERNETES_NAMESPACE }}
이 워크플로우는 AWS EKS 클러스터에 OIDC를 통해 인증하고 kubectl을 사용하여 배포하는 예시입니다. k8s/deployment.yaml 파일에는 배포할 애플리케이션의 Kubernetes Deployment 및 Service 정의가 포함되어야 합니다. 실제 프로덕션 환경에서는 Kustomize나 Helm과 같은 도구를 사용하여 Kubernetes 매니페스트를 관리하고 배포하는 것이 일반적입니다.
멀티-환경 CI/CD 및 승인 프로세스 적용
대부분의 CI/CD 파이프라인은 개발, 스테이징, 프로덕션과 같은 여러 환경을 거칩니다. GitHub Actions는 environment 기능을 통해 이러한 다중 환경 배포를 지원하며, 각 환경에 대한 비밀(Secrets) 관리, 보호 규칙, 그리고 수동 승인(manual approval)을 설정할 수 있습니다.
# .github/workflows/multi-env-ci-cd.yml
name: Multi-Environment CI/CD Pipeline
on:
push:
branches:
- main
env:
DOCKER_IMAGE_NAME: my-node-app
DOCKER_REGISTRY: ghcr.io/${{ github.repository_owner }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.tags }}
permissions:
contents: read
packages: write
id-token: write
steps:
# Docker 이미지 빌드 및 푸시 (이전 워크플로우의 build-and-push Job과 유사)
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
tags: |
type=sha,format=long # Git SHA를 태그로 사용
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
runs-on: ubuntu-latest
needs: build # build Job이 성공해야 실행
environment: staging # GitHub 환경 설정
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS Credentials for Staging
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-eks-staging-role
aws-region: ap-northeast-2
- name: Setup Kubeconfig for Staging
run: |
aws eks update-kubeconfig --name my-staging-cluster --region ap-northeast-2
- name: Deploy to Staging Kubernetes
run: |
kubectl config use-context arn:aws:eks:ap-northeast-2:123456789012:cluster/my-staging-cluster
kubectl set image deployment/my-node-app my-node-app=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.build.outputs.image_tag }} -n staging
kubectl rollout status deployment/my-node-app -n staging
deploy-production:
runs-on: ubuntu-latest
needs: deploy-staging # staging 배포가 성공해야 실행
environment: production # GitHub 환경 설정 (수동 승인 필요)
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS Credentials for Production
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-eks-production-role
aws-region: ap-northeast-2
- name: Setup Kubeconfig for Production
run: |
aws eks update-kubeconfig --name my-production-cluster --region ap-northeast-2
- name: Deploy to Production Kubernetes
run: |
kubectl config use-context arn:aws:eks:ap-northeast-2:123456789012:cluster/my-production-cluster
kubectl set image deployment/my-node-app my-node-app=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.build.outputs.image_tag }} -n production
kubectl rollout status deployment/my-node-app -n production
이 워크플로우는 build -> deploy-staging -> deploy-production 순서로 진행됩니다. environment를 사용하여 각 배포 단계에 맞는 보안 정책과 승인 프로세스를 적용할 수 있습니다. 특히 production 환경에는 수동 승인(Manual Approval)을 설정하여, 배포 전 사람의 검토를 거치도록 할 수 있습니다. 이는 GitHub 저장소 설정의 "Environments" 탭에서 구성할 수 있습니다.
인프라스트럭처 프로비저닝 (IaC) 통합
DevOps에서 CI/CD는 애플리케이션 배포뿐만 아니라 인프라스트럭처 관리에도 확장됩니다. Terraform과 같은 IaC(Infrastructure as Code) 도구를 GitHub Actions와 통합하여 클라우드 인프라를 자동으로 프로비저닝하고 관리할 수 있습니다.
# .github/workflows/terraform-infra.yml
name: Terraform Infrastructure Management
on:
push:
branches:
- main
paths:
- 'terraform/**' # terraform 디렉터리 변경 시 트리거
workflow_dispatch: # 수동 실행 허용
env:
TF_WORKING_DIR: ./terraform # Terraform 파일이 있는 디렉터리
jobs:
terraform-plan:
runs-on: ubuntu-latest
environment: dev # 개발 환경에서 plan 실행
permissions:
id-token: write # OIDC를 위한 권한
contents: read
pull-requests: write # PR에 plan 결과 코멘트 작성
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-terraform-dev
aws-region: ap-northeast-2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0 # 특정 Terraform 버전 지정
- name: Terraform Init
id: init
run: terraform init -backend-config="bucket=${{ secrets.TF_STATE_BUCKET }}" -backend-config="key=dev/terraform.tfstate" -backend-config="region=ap-northeast-2"
working-directory: ${{ env.TF_WORKING_DIR }}
- name: Terraform Plan
id: plan
run: terraform plan -no-color -input=false -out=tfplan
working-directory: ${{ env.TF_WORKING_DIR }}
continue-on-error: true # plan 실패해도 다음 스텝 진행 (코멘트 남기기 위함)
- name: Update Pull Request with Plan Output
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const output = `#### Terraform Plan 📝
\`\`\`
${process.env.PLAN_OUTPUT}
\`\`\`
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
env:
PLAN_OUTPUT: ${{ steps.plan.outputs.stdout }}
terraform-apply:
runs-on: ubuntu-latest
needs: terraform-plan # plan Job이 성공해야 실행
environment: production # 프로덕션 환경 (수동 승인 필요)
if: github.event_name == 'push' && github.ref == 'refs/heads/main' # main 브랜치 푸시에만 apply 실행
permissions:
id-token: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-terraform-prod
aws-region: ap-northeast-2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0
- name: Terraform Init
run: terraform init -backend-config="bucket=${{ secrets.TF_STATE_BUCKET }}" -backend-config="key=prod/terraform.tfstate" -backend-config="region=ap-northeast-2"
working-directory: ${{ env.TF_WORKING_DIR }}
- name: Terraform Apply
run: terraform apply -input=false -auto-approve
working-directory: ${{ env.TF_WORKING_DIR }}
이 워크플로우는 terraform 디렉터리의 변경 사항이 main 브랜치에 푸시되거나 수동으로 트리거될 때 실행됩니다. terraform-plan Job은 변경 사항을 검토하고, Pull Request가 있다면 그 결과를 코멘트로 남깁니다. terraform-apply Job은 plan Job이 성공하고 main 브랜치에 푸시될 때만 실행되며, production 환경 보호 규칙에 따라 수동 승인을 거친 후 인프라를 실제로 변경합니다. 이때 AWS OIDC를 사용하여 AWS 자격 증명을 안전하게 획득합니다.
워크플로우 보안 강화 및 모범 사례
GitHub Actions 워크플로우의 보안은 매우 중요합니다. 다음은 몇 가지 모범 사례입니다.
- Secret 관리: 민감한 정보(API 키, 클라우드 자격 증명)는 반드시 GitHub Secrets에 저장하고, 워크플로우에서
secrets.<SECRET_NAME>형태로 참조해야 합니다. 로그에 Secret 값이 노출되지 않도록 주의해야 합니다. - OpenID Connect (OIDC) 사용: 클라우드 환경(AWS, GCP, Azure)에 배포할 때, Secret을 직접 저장하는 대신 OIDC를 사용하여 임시 자격 증명을 획득하는 것이 가장 안전합니다. GitHub Actions가 클라우드 IAM 역할에 직접 인증하여 특정 권한만 부여받도록 설정할 수 있습니다.
- 최소 권한 원칙: 워크플로우에 부여되는 권한은 해당 작업을 수행하는 데 필요한 최소한의 권한으로 제한해야 합니다. 특히
GITHUB_TOKEN의 권한을permissions키워드를 사용하여 세밀하게 제어할 수 있습니다. - 환경 보호 규칙:
environment기능을 사용하여 특정 환경(예:production)에 대한 접근을 제한하고, 수동 승인, 특정 사용자/팀에 의한 배포 제한 등의 보호 규칙을 설정합니다. -
CODEOWNERS활용:CODEOWNERS파일을 사용하여 워크플로우 파일(.github/workflows/)의 변경 사항에 대한 검토자를 지정할 수 있습니다. 이는 악의적인 변경이나 실수로 인한 문제를 방지하는 데 도움이 됩니다. - 외부 Action 검증: GitHub Marketplace의 외부 Action을 사용할 때는 해당 Action의 소스 코드, 커뮤니티 평판, 유지보수 상태 등을 신중하게 검토해야 합니다. 가능하다면 특정 커밋 SHA에 고정하여 사용하고, 자체적으로 호스팅하는 Action을 사용하는 것도 고려할 수 있습니다.
- 로그 및 모니터링: 워크플로우 실행 로그를 주기적으로 검토하고, 실패 시 알림을 설정하여 문제를 신속하게 파악하고 대응해야 합니다.
마무리
이 글에서는 GitHub Actions를 활용한 Docker 이미지 빌드, Kubernetes 배포, 그리고 Terraform을 이용한 인프라스트럭처 프로비저닝에 이르는 고급 CI/CD 워크플로우 구축 전략을 살펴보았습니다. 각 섹션에서 제공된 예시들은 GitHub Actions가 DevOps 파이프라인을 얼마나 강력하고 유연하게 자동화할 수 있는지 보여줍니다.
GitHub Actions는 지속적인 통합과 배포를 넘어, 인프라스트럭처 관리, 보안 검사, 코드 품질 분석 등 개발 프로세스의 거의 모든 측면을 자동화할 수 있는 잠재력을 가지고 있습니다. 이 글에서 다룬 내용들을 바탕으로 여러분의 프로젝트에 최적화된 고급 워크플로우를 구축하고, 더욱 효율적이고 안정적인 DevOps 환경을 만들어 나가시길 바랍니다.
관련 게시글
GitOps Workflow 설계 가이드: Kubernetes와 CI/CD를 활용한 자동화
GitOps는 선언적 인프라 및 애플리케이션 관리를 위한 강력한 패러다임입니다. 이 가이드에서는 Git을 Single Source of Truth로 활용하여 Kubernetes 환경에서 안정적이고 효율적인 GitOps 워크플로우를 설계하는 방법을 Docker, CI/CD 자동화 예시와 함께 상세히 설명합니다.
Terraform Infrastructure Automation: DevOps 엔지니어링 가이드
Terraform을 활용한 인프라 자동화의 핵심 개념부터 실제 AWS 환경에서의 배포 예시까지, DevOps 엔지니어링 관점에서 자세히 알아봅니다. IaC, Docker, Kubernetes, CI/CD 파이프라인 통합 전략을 다룹니다.
Terraform Infrastructure Automation Guide: DevOps 효율성 극대화
Terraform을 활용한 인프라 자동화 가이드입니다. IaC 개념부터 AWS 리소스 프로비저닝, Docker/Kubernetes 환경 구축, CI/CD 연동까지 DevOps 엔지니어링 관점에서 자세히 다룹니다.