GitHub Actions Docker CI 파이프라인 구조 이해하기: 멀티 아키텍처 빌드부터 캐시 전략

2026. 1. 3. 12:44·Network

시작하며

최근 프로젝트에서 6개의 Go 서비스, 1개의 Spring 서비스 MSA로 분리하여  AWS EKS에 배포하는 CI/CD 파이프라인을 구축했습니다. 단순히 Docker 이미지를 빌드하는 것을 넘어서, 멀티 아키텍처 지원, 캐시 최적화, GitOps 연동까지 고려하다 보니 꽤 복잡한 구조가 되었습니다.

처음에는 "그냥 docker build 하면 되는 거 아닌가?" 라고 생각했지만, 실제로는 QEMU 에뮬레이션, BuildKit 캐시, GitHub Actions 매트릭스 전략 등 여러 개념을 이해해야 했습니다. 특히 "네이티브 빌드가 뭐지?", "캐시는 어디에 저장되는 거지?", "공통 패키지는 왜 매번 복사되는 거지?" 같은 질문들이 끊임없이 생겼습니다.

이 글에서는 저희 프로젝트의 CI 파이프라인 구조를 차근차근 설명하면서, 제가 헷갈렸던 개념들을 정리해보려고 합니다. 

CI 파이프라인 전체 구조

저희 워크플로우는 크게 4단계로 구성됩니다:

1. validate-branch
   └─ 브랜치 검증 (service-deploy-dev, service-deploy-prod만 허용)

2. detect-changes
   └─ 변경된 서비스 감지 (dorny/paths-filter 사용)
   └─ 환경 설정 (dev/prod)

3. build-platform (병렬 실행)
   └─ 각 서비스 × 각 플랫폼 네이티브 빌드
   └─ 다이제스트 아티팩트 업로드

4. merge-manifests (병렬 실행)
   └─ 플랫폼별 이미지를 매니페스트 리스트로 통합
   └─ ECR에 최종 이미지 푸시

5. update-gitops-manifests
   └─ k8s-deploy-{env} 브랜치에 이미지 태그 업데이트
   └─ ArgoCD 자동 동기화 트리거

하나씩 자세히 살펴보겠습니다.

Stage 1: 변경 감지 (detect-changes)

역할

Git 커밋에서 어떤 서비스가 변경되었는지 자동으로 감지합니다.

detect-changes:
  runs-on: ubuntu-latest
  outputs:
    services: ${{ steps.set-services.outputs.services }}
    environment: ${{ steps.env-config.outputs.environment }}
  
  steps:
    - uses: dorny/paths-filter@v2
      id: filter
      with:
        filters: |
          common-package:
            - 'packages/wealist-advanced-go-pkg/**'
          board-service:
            - 'services/board-service/**'
          user-service:
            - 'services/user-service/**'
          # ... 다른 서비스들

동작 방식

일반 서비스 변경:

커밋 내용:
└─ services/user-service/handler.go 수정

detect-changes 결과:
└─ services: ["user-service"]

공통 패키지 변경:

커밋 내용:
└─ packages/wealist-advanced-go-pkg/logger/logger.go 수정

detect-changes 결과:
└─ services: ["board-service", "chat-service", "noti-service", 
              "storage-service", "user-service", "video-service"]

→ 공통 패키지를 사용하는 모든 Go 서비스 빌드!

이렇게 감지된 서비스 목록이 다음 단계의 매트릭스로 전달됩니다.

Stage 2: 멀티 아키텍처 네이티브 빌드 (build-platform)

매트릭스 전략

이 부분이 가장 핵심입니다. GitHub Actions의 매트릭스 기능을 사용해 서비스 × 플랫폼 조합으로 Job을 생성합니다:

build-platform:
  runs-on: ${{ matrix.runner }}
  
  strategy:
    matrix:
      service: ${{ fromJSON(needs.detect-changes.outputs.services) }}
      platform: [linux/amd64, linux/arm64]
      include:
        - platform: linux/amd64
          runner: ubuntu-latest        # x86 러너
        - platform: linux/arm64
          runner: ubuntu-24.04-arm     # ARM 러너

실제 Job 생성 예시

시나리오: user-service만 변경됨

생성되는 Job:

Job 1:
├─ service: user-service
├─ platform: linux/amd64
├─ runner: ubuntu-latest (x86 CPU)
└─ 실행: x86 러너에서 amd64 이미지 빌드

Job 2:
├─ service: user-service
├─ platform: linux/arm64
├─ runner: ubuntu-24.04-arm (ARM CPU)
└─ 실행: ARM 러너에서 arm64 이미지 빌드

⚡ 두 Job이 동시에 병렬 실행됩니다!

시나리오: board-service + user-service 변경됨

생성되는 Job:

Job 1: board-service + linux/amd64 (x86 러너)
Job 2: board-service + linux/arm64 (ARM 러너)
Job 3: user-service + linux/amd64 (x86 러너)
Job 4: user-service + linux/arm64 (ARM 러너)

⚡ 4개 Job 모두 동시에 병렬 실행!

네이티브 빌드란?

핵심 개념:

  • 네이티브 빌드: CPU 아키텍처와 빌드 타겟이 일치
  • 크로스 빌드 (QEMU): CPU 아키텍처와 빌드 타겟이 다름
┌─────────────────────────────────────────┐
│ x86 러너에서 amd64 이미지 빌드          │
├─────────────────────────────────────────┤
│ 러너 CPU: x86_64 (Intel/AMD)            │
│ 빌드 타겟: linux/amd64                  │
│ 결과: 네이티브 빌드 ✅                   │
│ CPU가 명령어를 직접 실행                 │
│ → 매우 빠름!                            │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ ARM 러너에서 arm64 이미지 빌드          │
├─────────────────────────────────────────┤
│ 러너 CPU: ARM64 (Graviton/Apple M)      │
│ 빌드 타겟: linux/arm64                  │
│ 결과: 네이티브 빌드 ✅                   │
│ CPU가 명령어를 직접 실행                 │
│ → 매우 빠름!                            │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ x86 러너에서 arm64 이미지 빌드 (QEMU)   │
├─────────────────────────────────────────┤
│ 러너 CPU: x86_64                        │
│ 빌드 타겟: linux/arm64                  │
│ 결과: 에뮬레이션 빌드 ❌                 │
│ QEMU가 ARM 명령어를 x86으로 변환         │
│ → 3-5배 느림! 💀                        │
└─────────────────────────────────────────┘

QEMU 에뮬레이션의 오버헤드

QEMU는 서로 다른 CPU 아키텍처 간에 명령어를 실시간 변환합니다:

ARM 명령어 1개 실행 과정:

[네이티브 - ARM CPU에서 실행]
ARM 명령어 → ARM CPU 직접 실행
소요 시간: 1 사이클 ✅

[QEMU - x86 CPU에서 실행]
ARM 명령어
  ↓ 1. QEMU가 명령어 분석
  ↓ 2. 동등한 x86 명령어 찾기
  ↓ 3. x86 명령어 여러 개로 변환 (5-10개)
  ↓ 4. x86 CPU에서 실행
  ↓ 5. 결과를 ARM 레지스터에 반영
x86 실행 결과
소요 시간: 10-50 사이클 ❌

→ 결과: 3-5배 느려짐

실제 빌드 시간 비교:

board-service 빌드 (단일 러너에서 멀티 플랫폼 빌드 시):
├─ amd64 빌드 (네이티브): 2분
└─ arm64 빌드 (QEMU): 8분 ← 4배 느림!
총: 10분 (순차 실행)

board-service 빌드 (플랫폼별 러너 사용 시):
├─ amd64 빌드 (x86 러너, 네이티브): 2분
└─ arm64 빌드 (ARM 러너, 네이티브): 2분
총: 2분 (병렬 실행) ⚡

빌드 결과물

각 Job은 플랫폼별 이미지를 ECR에 푸시하고, 다이제스트(이미지 해시)를 아티팩트로 업로드합니다:

steps:
  - name: Build and push by digest
    id: build
    uses: docker/build-push-action@v5
    with:
      platforms: ${{ matrix.platform }}
      push: true
      tags: ${{ steps.tags.outputs.image_uri }}:${{ steps.tags.outputs.digest_tag }}
  
  - name: Export digest
    run: |
      mkdir -p /tmp/digests/${{ matrix.service }}
      digest="${{ steps.build.outputs.digest }}"
      touch "/tmp/digests/${{ matrix.service }}/${digest#sha256:}"
  
  - name: Upload digest
    uses: actions/upload-artifact@v4
    with:
      name: digests-${{ matrix.service }}-${{ steps.platform.outputs.pair }}
      path: /tmp/digests/${{ matrix.service }}/*

ECR에 생성되는 이미지:

user-service:42-abc1234-linux-amd64  ← amd64 이미지
user-service:42-abc1234-linux-arm64  ← arm64 이미지

업로드되는 아티팩트:

digests-user-service-linux-amd64
└─ sha256:abc123def456...

digests-user-service-linux-arm64
└─ sha256:789ghi012jkl...

Stage 3: 매니페스트 리스트 병합 (merge-manifests)

역할

플랫폼별로 나뉘어 있던 이미지들을 하나의 매니페스트 리스트로 통합합니다.

merge-manifests:
  needs: build-platform
  strategy:
    matrix:
      service: ${{ fromJSON(needs.detect-changes.outputs.services) }}
  
  steps:
    - name: Download digests
      uses: actions/download-artifact@v4
      with:
        pattern: digests-${{ matrix.service }}-*
        merge-multiple: true
    
    - name: Create manifest list and push
      run: |
        docker buildx imagetools create \
          -t ${IMAGE_URI}:${TAG} \
          ${IMAGE_URI}:${TAG}-linux-amd64 \
          ${IMAGE_URI}:${TAG}-linux-arm64
    
    - name: Cleanup platform-specific tags
      run: |
        # 임시 플랫폼별 태그 삭제
        aws ecr batch-delete-image \
          --repository-name $(echo ${IMAGE_URI} | cut -d'/' -f2-) \
          --image-ids imageTag=${TAG}-linux-amd64
        aws ecr batch-delete-image \
          --repository-name $(echo ${IMAGE_URI} | cut -d'/' -f2-) \
          --image-ids imageTag=${TAG}-linux-arm64

매니페스트 리스트란?

매니페스트 리스트는 여러 플랫폼의 이미지를 하나로 묶은 메타데이터 파일입니다:

user-service:42-abc1234 (매니페스트 리스트)
├─ linux/amd64 → sha256:abc123def456...
│  └─ 실제 amd64 이미지를 가리키는 포인터
│
└─ linux/arm64 → sha256:789ghi012jkl...
   └─ 실제 arm64 이미지를 가리키는 포인터

핵심:

  • 매니페스트 리스트 자체는 이미지가 아니라 "목차" 역할
  • 실제 이미지 데이터는 각 플랫폼별로 별도 저장
  • Docker/Kubernetes가 pull할 때 자동으로 적절한 이미지 선택

Kubernetes에서의 자동 플랫폼 선택

이것이 매니페스트 리스트의 가장 큰 장점입니다:

EKS 클러스터:

┌──────────────────────────────────────┐
│ Intel 노드 (c5.2xlarge)              │
├──────────────────────────────────────┤
│ kubelet이 이미지 pull 요청:          │
│ user-service:42-abc1234              │
│  ↓                                   │
│ kubelet이 노드 아키텍처 확인: amd64  │
│  ↓                                   │
│ 매니페스트 리스트에서 amd64 선택     │
│  ↓                                   │
│ sha256:abc123... 이미지 pull ✅      │
│  ↓                                   │
│ 컨테이너 실행 (100% 성능)            │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│ Graviton 노드 (c6g.2xlarge)          │
├──────────────────────────────────────┤
│ kubelet이 이미지 pull 요청:          │
│ user-service:42-abc1234 (동일!)      │
│  ↓                                   │
│ kubelet이 노드 아키텍처 확인: arm64  │
│  ↓                                   │
│ 매니페스트 리스트에서 arm64 선택     │
│  ↓                                   │
│ sha256:789ghi... 이미지 pull ✅      │
│  ↓                                   │
│ 컨테이너 실행 (100% 성능, 네이티브!) │
└──────────────────────────────────────┘

개발자는 아무것도 신경 쓸 필요가 없습니다!

  • Kubernetes manifest에는 image: user-service:42-abc1234만 적으면 됨
  • 노드 타입에 관계없이 항상 네이티브 이미지가 실행됨

만약 단일 아키텍처(amd64만)였다면?

EKS 클러스터 (단일 아키텍처 이미지):

Intel 노드:
└─ user-service:42-abc1234 (amd64만 존재)
   └─ 네이티브 실행 ✅ (100% 성능)

Graviton 노드:
└─ user-service:42-abc1234 (amd64만 존재)
   └─ QEMU 에뮬레이션으로 실행 ❌
      └─ 50-70% 성능만 나옴
      └─ Graviton의 비용 이점이 상쇄됨 💸

BuildKit 캐시 시스템 이해하기

Dockerfile 레이어 구조

Docker 이미지는 여러 레이어의 스택입니다:

FROM public.ecr.aws/docker/library/golang:1.24-bookworm AS builder

# Layer 1
RUN apt-get update && apt-get install -y git ca-certificates tzdata

# Layer 2
WORKDIR /workspace

# Layer 3
COPY packages/wealist-advanced-go-pkg/ ./packages/

# Layer 4
COPY services/board-service/go.mod go.sum ./

# Layer 5
RUN go mod download

# Layer 6
COPY services/board-service/ ./

# Layer 7
RUN go build -o board-api ./cmd/api

레이어 캐싱 원리:

빌드 1회차:
├─ Layer 1-7 모두 실행
└─ 각 레이어를 캐시에 저장

빌드 2회차 (소스 코드만 변경):
├─ Layer 1-5: CACHED ✅ (재사용)
├─ Layer 6: 변경됨! ❌ (재실행)
└─ Layer 7: Layer 6이 변경되어 재실행 ❌

레이어 배치 전략

핵심 원칙: 변경 빈도가 낮은 것부터 높은 순서로 배치

변경 빈도 (낮음 → 높음):
베이스 이미지 < 시스템 패키지 < 공통 패키지 < go.mod < 소스 코드

Dockerfile 순서:
FROM golang:1.24          ← 거의 안 바뀜
RUN apt-get install       ← 거의 안 바뀜
COPY packages/            ← 가끔 바뀜
COPY go.mod go.sum        ← 가끔 바뀜
RUN go mod download       ← go.mod 변경 시만
COPY source/              ← 자주 바뀜
RUN go build              ← 소스 변경 시만

BuildKit 캐시 마운트: 레이어 캐싱의 한계 극복

일반 레이어 캐싱의 문제:

# Layer 5
COPY services/board-service/ ./  ← 소스 변경!

# Layer 6
RUN go mod download  ← Layer 5가 변경되어 재실행
                        /go/pkg/mod가 비어있음
                        의존성 전체 재다운로드 ❌

BuildKit 캐시 마운트 사용:

# Layer 5
COPY services/board-service/ ./  ← 소스 변경!

# Layer 6
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download
    
# BuildKit이 /go/pkg/mod를 별도 캐시 영역에 마운트
# Layer 5가 변경되어도 /go/pkg/mod 내용은 유지됨 ✅
# 의존성 재다운로드 불필요!

두 가지 캐시 시스템:

┌──────────────────────────────────────┐
│ Docker 레이어 캐시                   │
├──────────────────────────────────────┤
│ - 각 RUN, COPY 명령어가 레이어       │
│ - 명령어 변경 시 이후 레이어 무효화  │
│ - 전체 레이어를 캐시                 │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│ BuildKit 캐시 마운트                 │
├──────────────────────────────────────┤
│ - 특정 디렉토리만 캐시               │
│ - 레이어 무효화와 독립적             │
│ - /go/pkg/mod, /root/.cache/go-build │
└──────────────────────────────────────┘

GitHub Actions 캐시 통합

BuildKit 캐시를 GitHub Actions 캐시 스토리지에 저장:

- uses: docker/build-push-action@v5
  with:
    cache-from: type=gha,scope=board-service-linux-amd64-dev
    cache-to: type=gha,mode=max,scope=board-service-linux-amd64-dev

캐시 저장 위치:

┌────────────────────────────────────────┐
│ GitHub Actions Cache API              │
│ (GitHub 클라우드 스토리지)            │
├────────────────────────────────────────┤
│ board-service-linux-amd64-dev:        │
│ ├─ layer-001.tar.gz (golang:1.24)     │
│ ├─ layer-002.tar.gz (apt-get)         │
│ ├─ layer-003.tar.gz (공통 패키지)     │
│ ├─ layer-004.tar.gz (go.mod)          │
│ ├─ layer-005.tar.gz (go mod download) │
│ └─ ...                                │
│ 총: ~600MB                            │
│ 보관 기간: 7일 (미사용 시 삭제)       │
└────────────────────────────────────────┘

mode=max vs mode=min:

mode=min (기본값):
└─ 최종 이미지 레이어만 저장
   └─ 캐시 크기: 50MB
   └─ 중간 빌드 레이어 없음
   └─ 다음 빌드 시 대부분 재실행 ❌

mode=max (권장):
└─ 모든 중간 레이어 저장
   └─ 캐시 크기: 600MB
   └─ golang 이미지, apt-get, go mod download 모두 포함
   └─ 다음 빌드 시 최대한 재사용 ✅

빌드 로그에서 캐시 확인하기

실제 로그를 보면 어떤 부분이 캐시되었는지 알 수 있습니다:

#12 [builder  2/11] RUN apt-get update && apt-get install ...
#12 CACHED  ← 캐시 사용!

#13 [builder  3/11] WORKDIR /workspace
#13 CACHED  ← 캐시 사용!

#14 [builder  4/11] COPY packages/wealist-advanced-go-pkg/ ...
#14 DONE 2.2s  ← 실제 실행 (캐시 미스 또는 변경됨)

#18 [builder  8/11] RUN --mount=type=cache,target=/go/pkg/mod go mod download
#18 DONE 2.9s  ← 실제 실행하지만 /go/pkg/mod는 캐시 마운트에서 가져옴

공통 패키지 중복 문제

문제 상황

9개 서비스 모두 packages/wealist-advanced-go-pkg/ 공통 패키지를 사용합니다:

# 모든 서비스의 Dockerfile에 공통적으로 존재
COPY packages/wealist-advanced-go-pkg/ ./packages/wealist-advanced-go-pkg/

현재 캐시 구조:

GitHub Actions Cache (10GB 제한):

board-service-linux-amd64-dev: 600MB
├─ golang:1.24 (450MB)
├─ apt-get (30MB)
├─ 공통 패키지 (50MB) ← 중복!
└─ ...

user-service-linux-amd64-dev: 580MB
├─ golang:1.24 (450MB)
├─ apt-get (30MB)
├─ 공통 패키지 (50MB) ← 중복!
└─ ...

storage-service-linux-amd64-dev: 620MB
├─ golang:1.24 (450MB)
├─ apt-get (30MB)
├─ 공통 패키지 (50MB) ← 중복!
└─ ...

... 9개 서비스 × 2개 플랫폼 = 18개 캐시
공통 패키지만: 50MB × 18 = 900MB 중복!

빌드 로그에서 확인

#14 [builder  4/11] COPY packages/wealist-advanced-go-pkg/ ./packages/wealist-advanced-go-pkg/
#14 DONE 2.2s

← 이 단계가 모든 서비스 빌드에서 매번 실행됨!

비효율:

  • 9개 서비스 × 2.2초 = 약 20초 낭비
  • 900MB 캐시 공간 낭비

왜 캐시가 공유되지 않을까?

캐시 스코프가 서비스별로 분리되어 있기 때문:

cache-from: type=gha,scope=board-service-linux-amd64-dev
cache-to: type=gha,mode=max,scope=board-service-linux-amd64-dev
           ↑
           서비스별로 다른 스코프
board-service는 board-service-* 캐시만 읽음
user-service는 user-service-* 캐시만 읽음
→ 공통 패키지 레이어가 각 캐시에 중복 저장됨

베이스 이미지의 이해

현재 사용 중인 베이스 이미지

FROM public.ecr.aws/docker/library/golang:1.24-bookworm AS builder

이것은:

  • AWS ECR Public Gallery에서 제공하는 공식 golang 이미지
  • Docker Hub의 golang:1.24-bookworm과 동일
  • AWS가 미러링해서 제공 (빠른 다운로드)

포함 내용:

golang:1.24-bookworm:
├─ Debian Bookworm OS
├─ Go 1.24 컴파일러 및 도구체인
├─ 기본 빌드 도구
└─ /workspace 디렉토리 (비어있음)

❌ packages/wealist-advanced-go-pkg/ 없음!

ECR Public vs Docker Hub

Docker Hub:
├─ URL: docker.io/library/golang:1.24-bookworm
├─ 속도: 보통
└─ Rate Limit: 익명 100 pulls/6시간

AWS ECR Public:
├─ URL: public.ecr.aws/docker/library/golang:1.24-bookworm
├─ 속도: 빠름 (AWS 인프라 사용 시) ⚡
└─ Rate Limit: 더 관대함

GitOps 연동: ArgoCD 자동 배포

update-gitops-manifests Job

빌드가 완료되면 k8s manifest를 업데이트하여 ArgoCD가 자동으로 배포하도록 합니다:

update-gitops-manifests:
  needs: [detect-changes, merge-manifests]
  
  steps:
    - name: Checkout k8s branch
      run: |
        # k8s-deploy-dev 또는 k8s-deploy-prod 브랜치 체크아웃
        git fetch origin ${K8S_BRANCH}
        git checkout ${K8S_BRANCH}
    
    - name: Update manifest files
      run: |
        for service in $(echo $SERVICES | jq -r '.[]'); do
          MANIFEST_FILE="k8s/argocd/apps/${ENV}/${service}.yaml"
          
          # image.tag 업데이트
          sed -i '/- name: image\.tag/{
            n
            s|value: ".*"|value: "'"${NEW_TAG}"'"|
          }' "$MANIFEST_FILE"
        done
    
    - name: Commit and push
      run: |
        git add .
        git commit -m "🚀 Update image tags (Build #${BUILD_NUMBER})"
        git push origin ${K8S_BRANCH}

전체 흐름

1. 코드 변경 (service-deploy-dev 브랜치에 push)
   └─ services/user-service/handler.go 수정

2. GitHub Actions 워크플로우 트리거
   ├─ detect-changes: user-service 감지
   ├─ build-platform: amd64, arm64 이미지 빌드 (병렬)
   ├─ merge-manifests: 매니페스트 리스트 생성
   └─ update-gitops-manifests

3. k8s-deploy-dev 브랜치 업데이트
   └─ k8s/argocd/apps/dev/user-service.yaml
      image.tag: "78-2ffb73c" (변경)

4. ArgoCD가 변경 감지
   └─ 자동으로 Git과 클러스터 동기화

5. EKS 클러스터에 배포
   └─ 새로운 이미지로 Pod 재시작

마치며

GitHub Actions로 Docker CI 파이프라인을 구축하면서 가장 많이 헷갈렸던 것은 "어떤 캐시가 어디에 저장되는지", "네이티브 빌드가 정확히 무엇인지" 같은 개념들이었습니다.

이 글에서는 기술적인 최적화보다는, 저희 프로젝트의 CI 구조와 각 단계가 어떻게 동작하는지에 초점을 맞춰 설명했습니다. 특히:

  • 매트릭스 전략으로 서비스 × 플랫폼 Job 생성
  • 네이티브 빌드 vs QEMU 에뮬레이션의 차이
  • 매니페스트 리스트와 Kubernetes의 자동 플랫폼 선택
  • BuildKit 캐시 마운트와 GitHub Actions 캐시의 관계
  • 공통 패키지 중복 저장 문제

이런 개념들을 이해하고 나니, 로그를 보는 눈도 달라지고 문제를 파악하기도 훨씬 쉬워졌습니다.

여러분의 프로젝트에도 비슷한 구조가 있다면, 이 글이 조금이나마 도움이 되길 바랍니다. 궁금한 점이나 다른 경험이 있으시면 댓글로 공유해주세요!

참고 자료

 

  • https://github.com/OrangesCloud/wealist-project-advanced-k8s
  • BuildKit - Cache Mounts
  • Docker Buildx - Cache
 

GitHub - OrangesCloud/wealist-project-advanced-k8s: 심화프로젝트 docker-compose, kind-k8s 완성버전(v1.1210)

심화프로젝트 docker-compose, kind-k8s 완성버전(v1.1210). Contribute to OrangesCloud/wealist-project-advanced-k8s development by creating an account on GitHub.

github.com

 

 

 

 

 

 

'Network' 카테고리의 다른 글

Istio로 대규모 트래픽 관리하기: 실전 적용 사례와 확장 전략  (2) 2026.01.10
EKS에서 ArgoCD로 GitOps 구축하기: 순환 의존성 해결부터 Single Sync까지  (0) 2026.01.05
실전 Kubernetes 보안: 4C 모델을 프로덕션 클러스터에 적용하며 배운 것들  (0) 2026.01.04
SealSecret vs AWS Secret Manager  (0) 2026.01.02
Prometheus 디스코드 웹훅  (0) 2025.10.02
'Network' 카테고리의 다른 글
  • EKS에서 ArgoCD로 GitOps 구축하기: 순환 의존성 해결부터 Single Sync까지
  • 실전 Kubernetes 보안: 4C 모델을 프로덕션 클러스터에 적용하며 배운 것들
  • SealSecret vs AWS Secret Manager
  • Prometheus 디스코드 웹훅
Ry-
Ry-
  • Ry-
    developer_Ryu
    Ry-
  • 전체
    오늘
    어제
    • 분류 전체보기 (97)
      • AI (14)
      • DB (3)
      • Book (6)
      • Development Environment (2)
        • Mac (2)
      • Frontend (8)
        • Flutter (6)
        • React (1)
        • React-Native (1)
      • Backend (19)
        • Spring (14)
        • Java (1)
        • Node.js (4)
      • Computer Science (27)
        • PS (20)
        • CS (7)
      • Network (12)
      • 이것저것 (3)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      aop
      오블완
      java
      spring
      chatgpt api
      백준
      node.js
      redis
      티스토리챌린지
      Baekjoon
      BOGGLE
      구글 로그인
      최대 힙
      mysql
      Kakao
      mongodb
      database
      몽고디비
      flutter
      스프링
      종만북
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.6
    Ry-
    GitHub Actions Docker CI 파이프라인 구조 이해하기: 멀티 아키텍처 빌드부터 캐시 전략
    상단으로

    티스토리툴바