서론: 설정 관리와 외부 접근의 중요성

컨테이너화된 애플리케이션을 운영하면서 가장 중요한 과제 중 하나는 설정의 분리입니다. 애플리케이션 코드와 설정을 분리하면 같은 이미지를 개발, 스테이징, 프로덕션 환경에서 재사용할 수 있습니다. Kubernetes는 이를 위해 ConfigMapSecret이라는 리소스를 제공합니다.

또한 클러스터 외부에서 내부 서비스에 접근하려면 적절한 라우팅 메커니즘이 필요합니다. Ingress는 HTTP/HTTPS 트래픽을 클러스터 내부 서비스로 라우팅하는 L7 로드밸런서 역할을 합니다. 이번 편에서는 이 세 가지 핵심 리소스를 상세히 알아보겠습니다.

1. 설정 관리의 중요성

1.1 왜 설정을 분리해야 하는가?

12-Factor App 방법론에서 강조하는 것처럼, 설정은 코드와 분리되어야 합니다:

  • 환경별 배포: 동일한 이미지를 다양한 환경(개발, 테스트, 프로덕션)에 배포할 수 있습니다
  • 보안 강화: 민감한 정보를 코드 저장소에 포함하지 않아도 됩니다
  • 유연한 변경: 설정 변경 시 이미지 재빌드 없이 적용할 수 있습니다
  • 팀 협업: 개발자와 운영자가 각자의 영역을 독립적으로 관리할 수 있습니다

1.2 Kubernetes의 설정 관리 방식

Kubernetes는 두 가지 리소스로 설정을 관리합니다:

리소스 용도 저장 방식
ConfigMap 일반 설정 데이터 평문(Plain text)
Secret 민감한 정보 Base64 인코딩

2. ConfigMap 생성과 사용

2.1 ConfigMap이란?

ConfigMap은 키-값 쌍으로 구성된 설정 데이터를 저장하는 Kubernetes 리소스입니다. 애플리케이션의 환경 변수, 설정 파일, 명령행 인자 등을 저장할 수 있습니다.

2.2 ConfigMap 생성 방법

방법 1: 리터럴 값으로 생성

# 단일 키-값 쌍
kubectl create configmap app-config --from-literal=APP_ENV=production

# 여러 키-값 쌍
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

방법 2: 파일에서 생성

# 단일 파일
kubectl create configmap nginx-config --from-file=nginx.conf

# 디렉토리 전체
kubectl create configmap app-configs --from-file=./configs/

방법 3: YAML 매니페스트로 생성

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  # 단순 키-값 쌍
  APP_ENV: "production"
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"

  # 여러 줄의 설정 파일
  application.properties: |
    server.port=8080
    spring.datasource.url=jdbc:mysql://mysql:3306/mydb
    spring.jpa.hibernate.ddl-auto=update
    logging.level.root=INFO

  # JSON 형식 설정
  config.json: |
    {
      "apiEndpoint": "https://api.example.com",
      "timeout": 30,
      "retryCount": 3
    }
kubectl apply -f configmap.yaml

2.3 ConfigMap 사용: 환경 변수

개별 키를 환경 변수로 사용

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    env:
    # 특정 키만 환경 변수로 주입
    - name: APPLICATION_ENV
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: APP_ENV
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: LOG_LEVEL

모든 키를 환경 변수로 사용

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    envFrom:
    - configMapRef:
        name: app-config
    # 접두사 추가 가능
    - configMapRef:
        name: app-config
      prefix: CONFIG_

2.4 ConfigMap 사용: 볼륨 마운트

설정 파일이 필요한 경우 볼륨으로 마운트할 수 있습니다:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - name: config-volume
      mountPath: /etc/nginx/conf.d
      readOnly: true
  volumes:
  - name: config-volume
    configMap:
      name: nginx-config
      # 특정 키만 마운트할 경우
      items:
      - key: nginx.conf
        path: default.conf

subPath를 사용한 특정 파일 마운트

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: config-volume
      mountPath: /app/config/application.properties
      subPath: application.properties
  volumes:
  - name: config-volume
    configMap:
      name: app-config

3. Secret 생성과 사용

3.1 Secret이란?

Secret은 비밀번호, OAuth 토큰, SSH 키와 같은 민감한 정보를 저장하는 리소스입니다. ConfigMap과 유사하지만, 데이터가 Base64로 인코딩되어 저장됩니다.

3.2 Secret 유형

유형 설명 사용 예
Opaque 임의의 사용자 정의 데이터 비밀번호, API 키
kubernetes.io/tls TLS 인증서 HTTPS 인증서
kubernetes.io/dockerconfigjson Docker 레지스트리 인증 Private 레지스트리 접근
kubernetes.io/basic-auth 기본 인증 정보 사용자명/비밀번호
kubernetes.io/ssh-auth SSH 인증 SSH 개인 키

3.3 Opaque Secret 생성

방법 1: 명령행으로 생성

# 리터럴 값으로 생성
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=s3cr3tP@ssw0rd

# 파일에서 생성
kubectl create secret generic ssh-key \
  --from-file=ssh-privatekey=/path/to/id_rsa

방법 2: YAML 매니페스트로 생성

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  # Base64 인코딩된 값
  # echo -n 'admin' | base64 -> YWRtaW4=
  username: YWRtaW4=
  # echo -n 's3cr3tP@ssw0rd' | base64 -> czNjcjN0UEBzc3cwcmQ=
  password: czNjcjN0UEBzc3cwcmQ=
---
# stringData 사용 (자동으로 Base64 인코딩)
apiVersion: v1
kind: Secret
metadata:
  name: api-credentials
type: Opaque
stringData:
  api-key: my-super-secret-api-key
  api-secret: another-secret-value

3.4 TLS Secret 생성

# TLS 인증서 Secret 생성
kubectl create secret tls tls-secret \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key
# tls-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
type: kubernetes.io/tls
data:
  tls.crt: |
    LS0tLS1CRUdJTi... (Base64 인코딩된 인증서)
  tls.key: |
    LS0tLS1CRUdJTi... (Base64 인코딩된 키)

3.5 Secret 사용하기

환경 변수로 주입

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password

볼륨으로 마운트

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: db-credentials
      # 파일 권한 설정
      defaultMode: 0400

3.6 Secret 보안 고려사항

Secret은 기본적으로 Base64 인코딩만 되어 있어 암호화되지 않습니다. 다음 보안 조치를 고려해야 합니다:

  • etcd 암호화: etcd에 저장되는 Secret을 암호화합니다
    # EncryptionConfiguration
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
        - secrets
        providers:
        - aescbc:
            keys:
            - name: key1
              secret: base64-encoded-32-byte-key
        - identity: {}
  • RBAC 제한: Secret에 대한 접근 권한을 최소화합니다
  • 외부 Secret 관리 도구 사용: HashiCorp Vault, AWS Secrets Manager, Azure Key Vault 등
  • Secret 로테이션: 정기적으로 Secret 값을 변경합니다
  • 감사 로깅: Secret 접근에 대한 감사 로그를 활성화합니다

4. Ingress란?

4.1 Ingress 개념

Ingress는 클러스터 외부에서 내부 서비스로의 HTTP/HTTPS 트래픽을 관리하는 API 오브젝트입니다. L7(애플리케이션 계층) 로드밸런서로서 다음 기능을 제공합니다:

  • 호스트 기반 라우팅: 도메인 이름에 따라 다른 서비스로 라우팅
  • 패스 기반 라우팅: URL 경로에 따라 다른 서비스로 라우팅
  • TLS/SSL 종료: HTTPS 트래픽 처리
  • 로드 밸런싱: 여러 Pod에 트래픽 분산

4.2 Ingress vs Service (NodePort/LoadBalancer)

특성 NodePort LoadBalancer Ingress
계층 L4 L4 L7
프로토콜 TCP/UDP TCP/UDP HTTP/HTTPS
URL 라우팅 불가 불가 가능
TLS 종료 불가 불가 가능
비용 무료 클라우드 비용 발생 단일 LB로 여러 서비스

5. Ingress Controller

5.1 Ingress Controller란?

Ingress 리소스는 선언적인 라우팅 규칙일 뿐이며, 실제로 작동하려면 Ingress Controller가 필요합니다. Ingress Controller는 Ingress 리소스를 감시하고 그에 맞게 로드밸런서를 구성합니다.

5.2 주요 Ingress Controller

Nginx Ingress Controller

# Nginx Ingress Controller 설치 (Helm)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install nginx-ingress ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace

# 또는 manifest로 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml

Traefik Ingress Controller

# Traefik 설치 (Helm)
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace

기타 Ingress Controller

  • HAProxy Ingress: 고성능 로드밸런싱
  • Contour: Envoy 기반
  • AWS ALB Ingress Controller: AWS Application Load Balancer 연동
  • GKE Ingress Controller: Google Cloud Load Balancer 연동

6. Ingress YAML 작성

6.1 기본 Ingress 구조

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    # Ingress Controller 특정 설정
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx  # 사용할 Ingress Controller 지정
  rules:
  - host: example.com      # 호스트 기반 라우팅
    http:
      paths:
      - path: /            # 패스 기반 라우팅
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80

6.2 pathType 옵션

  • Exact: 정확히 일치하는 경로만 매칭
  • Prefix: 지정된 경로로 시작하는 모든 요청 매칭
  • ImplementationSpecific: Ingress Controller에 따라 동작

7. 호스트/패스 기반 라우팅

7.1 호스트 기반 라우팅

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-based-ingress
spec:
  ingressClassName: nginx
  rules:
  # api.example.com -> api-service
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

  # web.example.com -> web-service
  - host: web.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

  # admin.example.com -> admin-service
  - host: admin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: admin-service
            port:
              number: 3000

7.2 패스 기반 라우팅

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: path-based-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      # example.com/api/* -> api-service
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: api-service
            port:
              number: 8080

      # example.com/web/* -> web-service
      - path: /web(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: web-service
            port:
              number: 80

      # example.com/* (기본) -> frontend-service
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

7.3 복합 라우팅 (호스트 + 패스)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: combined-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /products
        pathType: Prefix
        backend:
          service:
            name: product-service
            port:
              number: 8080
      - path: /orders
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 8080
      - path: /users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 8080

8. TLS 설정

8.1 TLS Secret 생성

# 자체 서명 인증서 생성 (테스트용)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key \
  -out tls.crt \
  -subj "/CN=example.com"

# TLS Secret 생성
kubectl create secret tls example-tls \
  --cert=tls.crt \
  --key=tls.key

8.2 Ingress에 TLS 적용

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.com
    - www.example.com
    secretName: example-tls
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

8.3 cert-manager를 이용한 자동 인증서 관리

# cert-manager 설치
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Let's Encrypt ClusterIssuer 설정
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx
---
# 자동 인증서 발급 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: auto-tls-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.com
    secretName: example-tls-auto  # cert-manager가 자동 생성
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

9. 실전 예제: 마이크로서비스 라우팅

# 완전한 마이크로서비스 Ingress 구성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: microservices-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      # API Gateway
      - path: /api/v1/users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 8080
      - path: /api/v1/products
        pathType: Prefix
        backend:
          service:
            name: product-service
            port:
              number: 8080
      - path: /api/v1/orders
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 8080
      # WebSocket
      - path: /ws
        pathType: Prefix
        backend:
          service:
            name: websocket-service
            port:
              number: 8081
      # 프론트엔드 (기본)
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

10. 결론

이번 편에서는 Kubernetes의 핵심 설정 관리 리소스인 ConfigMap과 Secret, 그리고 외부 트래픽을 관리하는 Ingress에 대해 알아보았습니다:

  • ConfigMap: 일반 설정 데이터를 관리하며, 환경 변수나 볼륨으로 Pod에 주입할 수 있습니다
  • Secret: 민감한 정보를 안전하게 저장하며, 추가적인 보안 조치가 필요합니다
  • Ingress: L7 로드밸런서로서 호스트/패스 기반 라우팅과 TLS 종료를 제공합니다
  • Ingress Controller: Ingress 리소스를 실제로 구현하는 컴포넌트입니다

다음 10편에서는 Kubernetes 패키지 매니저인 HelmCI/CD 파이프라인 구축에 대해 알아보겠습니다. Helm Chart를 활용한 애플리케이션 배포와 GitOps 기반의 자동화된 배포 전략을 다룰 예정입니다.