HAProxy 레이어 7 부하 분산 참조 구현

Google Distributed Cloud (GDC) 에어 갭은 기본 제공 관리형 레이어 4 (L4) 부하 분산기를 제공하지만 많은 엔터프라이즈 애플리케이션에는 호스트 기반 라우팅, 중앙 집중식 TLS 관리, 복잡한 트래픽 분할과 같은 고급 레이어 7 (L7) 기능이 필요합니다. 이전에는 Kubernetes 커뮤니티에서 현재 기능이 고정된 것으로 간주되는 인그레스 API를 사용하여 이 작업을 수행했습니다.

이 참조 아키텍처는 자체 관리형 레이어 7 부하 분산 솔루션을 제공합니다. 고객은 GDC 표준 클러스터에 널리 사용되는 HAProxy 오픈소스 컨트롤러를 배포하여 L7 트래픽을 하이브리드 환경으로 원활하게 라우팅할 수 있습니다. 이 아키텍처는 TLS 종료(HTTPRoute)를 사용하여 서버 이름 표시 (SNI)를 기반으로 트래픽을 기본 제공 컨테이너화된 포드와 외부 가상 머신에서 호스팅되는 애플리케이션으로 라우팅합니다.

아키텍처

GDC 에어갭의 HAProxy 레이어 7 부하 분산 아키텍처 다이어그램

솔루션의 주요 구성요소는 다음과 같습니다.

  • 클라이언트: 애플리케이션과 상호작용하기 위해 HTTPS 요청을 시작하는 항목입니다.
  • GDC 표준 클러스터: GDC는 Kubernetes Vanilla 클러스터를 만드는 기본 제공 방법을 제공합니다. 이 솔루션에서 클러스터는 L7 LB 및 컨트롤러와 워크로드, 외부 VM의 헤드리스 서비스를 호스팅합니다.
  • GDC L4 부하 분산기: 진입점 역할을 하는 기본 제공 L4 부하 분산기로, TCP/443 트래픽을 컨트롤러를 실행하는 Kubernetes 포드에 직접 분산합니다.
  • 인그레스 컨트롤러: 표준 클러스터에서 실행되는 HAProxy 연산자입니다. Ingress 리소스를 모니터링하고 기본 프록시를 동적으로 업데이트합니다. HAProxy In그레스 컨트롤러는 다음 구현에서 사용됩니다.
  • 인그레스: 물리적 수신 대기 포트 (443)와 TLS 종료가 포함된 SNI 기반 호스트 라우팅 규칙을 정의하는 표준화된 Kubernetes 리소스입니다.
  • 컨테이너화된 워크로드 (포드): 일반 Kubernetes Service를 사용하여 내부적으로 노출되는 표준 Kubernetes 배포입니다.
  • VM 기반 워크로드 (외부): 프로젝트 네트워크의 외부 VM에서 호스팅되는 워크로드로, 헤드리스 Kubernetes Service 및 VM의 직접 IP가 포함된 커스텀 엔드포인트를 사용하여 프록시에 노출됩니다.
  • Harbor Registry: 에어 갭 환경에서 프록시 및 애플리케이션 이미지를 저장하고 제공하는 데 사용되는 비공개 컨테이너 레지스트리입니다.

표준 클러스터에서 다음 세 가지 네임스페이스를 만듭니다.

  • load-balancer 네임스페이스는 HAProxy 인그레스 컨트롤러와 HAProxy 부하 분산기 워크로드를 호스팅합니다.

    부하 분산기 네임스페이스 리소스입니다.

  • hello-app 네임스페이스는 데모 컨테이너 워크로드의 Deployment, Service, Ingress를 호스팅합니다.

    hello-app 네임스페이스 리소스입니다.

  • vm-app 네임스페이스는 외부 VM IP를 노출하는 헤드리스 서비스, 외부 IP를 가리키는 EndpointSlice, Ingress를 호스팅합니다.

    vm-app 네임스페이스 리소스입니다.

시작하기 전에

이 솔루션을 배포하기 전에 다음 기본 요건이 충족되었는지 확인합니다.

  • 필요한 소프트웨어: helm, docker, kubectl
  • CLI 로그인 및 로컬 설정: GDC 콘솔에서 gdcloud CLI를 다운로드하고 로컬에서 환경을 설정합니다.

    export USER_NAME="USER_NAME"
    export PROJECT_ID="PROJECT_ID"
    export ZONE="ZONE"
    export ORG_NAME="ORG_NAME"
    export GDC_URL="GDC_URL"
    
    gdcloud components install gdcloud-k8s-auth-plugin
    
    gdcloud config set core/organization_console_url \
      https://console.$ORG_NAME.$ZONE.$GDC_URL
    gdcloud config set core/zone $ZONE
    gdcloud config set core/project ${PROJECT_ID}
    
    gdcloud auth login # use --login-config-cert option in case of TLS error
    
  • 프로젝트 설정: GDC 에어 갭 환경에서 리소스를 저장할 프로젝트를 만듭니다.

    gdcloud projects create $PROJECT_ID
    
  • IAM 역할: 사용자에게 Kubernetes 리소스를 관리할 수 있는 클러스터 관리자표준 클러스터 관리자 역할과 이미지를 푸시할 수 있는 Harbor 인스턴스 관리자 역할을 부여합니다.

    # Grant standard cluster and cluster admin roles
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member="user:${USER_NAME}" \
      --role=cluster-admin
    
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member="user:${USER_NAME}" \
      --role=standard-cluster-admin
    
    # Grant Harbor instance admin role
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member="user:${USER_NAME}" \
      --role=harbor-instance-admin
    

표준 클러스터 만들기

이 섹션에서는 GDC 에어 갭 환경 내에서 표준 Kubernetes 클러스터를 설정하는 프로세스를 안내합니다. 표준 클러스터는 HAProxy 인그레스 컨트롤러 및 커스텀 애플리케이션을 비롯한 다양한 워크로드를 배포하기 위한 유연하고 안정적인 기반을 제공합니다. 다음 단계를 통해 클러스터가 올바르게 구성되고 후속 배포에 액세스할 수 있도록 합니다.

  1. 다음을 실행하여 사용 가능한 가상 머신 이미지 유형을 식별합니다.

    gdcloud compute machine-types list
    
  2. 클러스터 작업자 노드에 적합한 머신 유형을 선택합니다. 이 튜토리얼에서는 vCPU가 4개 이상 인 머신 유형을 사용하는 것이 좋습니다.

    export MACHINE_TYPE="MACHINE_TYPE"
    
  3. 관리 API 서버 kubeconfig를 가져오고 별칭을 설정합니다.

    export CLUSTER_NAME="CLUSTER_NAME"
    
    KUBECONFIG=kubeconfig-admin.yaml gdcloud clusters \
      get-credentials ${ORG_NAME}-admin
    
    alias km="kubectl --kubeconfig kubeconfig-admin.yaml"
    
  4. 작업자 노드가 2개인 표준 클러스터를 만듭니다.

    km create -f - <<EOF
    apiVersion: cluster.gdc.goog/v1
    kind: Cluster
    metadata:
      name: ${CLUSTER_NAME}
      namespace: ${PROJECT_ID}
    spec:
      nodePools:
      - machineTypeName: ${MACHINE_TYPE}
        nodeCount: 2
        name: ${CLUSTER_NAME}-node-pool
    EOF
    

    사용 가능한 옵션에 대한 자세한 내용은 문서를 참고하세요.

    표준 클러스터 생성을 완료하는 데 최대 60분이 걸릴 수 있습니다. 상태를 확인하려면 다음 명령어를 사용합니다.

    km get clusters/${CLUSTER_NAME} \
      -n ${PROJECT_ID} \
      --watch
    

    클러스터가 준비되면 다음과 같이 상태가 실행 중으로 표시됩니다.

    NAME         STATE     K8S VERSION
    my-cluster   Running   1.30.12-gke.300
    
  5. 클러스터가 준비되면 사용자 인증 정보를 가져옵니다.

    KUBECONFIG=kubeconfig-${CLUSTER_NAME}.yaml gdcloud clusters \
      get-credentials ${CLUSTER_NAME} \
      --standard \
      --project ${PROJECT_ID} \
      --zone ${ZONE}
    
  6. 이 가이드의 나머지 부분에서 kubectl 명령어를 더 간결하게 유지하기 위해 별칭을 만듭니다. 이 별칭은 표준 클러스터와 상호작용하는 데 사용됩니다.

    alias kk="kubectl --kubeconfig kubeconfig-${CLUSTER_NAME}.yaml"
    
  7. 컨트롤러, 'hello-app' 데모 컨테이너화된 앱, VM 기반 데모 앱의 네임스페이스를 만듭니다.

    kk create namespace load-balancer
    kk create namespace hello-app
    kk create namespace vm-app
    

Harbor Registry 만들기 및 통합

Harbor는 GDC 에어 갭에서 기본 제공 지원되는 컨테이너 이미지 레지스트리입니다. 이 섹션에서는 사용자 인증 정보 및 보안 비밀을 구성하여 이미지를 안전하게 가져오고 푸시하는 기능을 포함하여 Harbor Registry를 표준 클러스터와 통합하는 단계를 안내합니다.

  1. 프로젝트에서 Harbor 인스턴스 를 만듭니다.
  2. Harbor 인스턴스에서 Harbor 프로젝트 를 만듭니다.
  3. 환경 변수를 설정합니다.

    export HARBOR_INSTANCE_NAME="HARBOR_INSTANCE_NAME"
    export HARBOR_INSTANCE_URL="HARBOR_INSTANCE_URL"
    export HARBOR_PROJECT="HARBOR_PROJECT"
    export IMAGE_PULL_SECRET_NAME="harbor-secret"
    
  4. 로봇 계정을 사용하여 Harbor 인스턴스에 로그인합니다.

    docker --config=./docker login ${HARBOR_INSTANCE_URL}
    
  5. 표준 클러스터에서 보안 비밀을 만듭니다.

    kk create secret docker-registry ${IMAGE_PULL_SECRET_NAME} \
      --from-file=.dockerconfigjson=./docker/config.json \
      -n load-balancer
    
    kk create secret docker-registry ${IMAGE_PULL_SECRET_NAME} \
      --from-file=.dockerconfigjson=./docker/config.json \
      -n hello-app
    

데모 컨테이너화된 앱 배포

이 섹션에서는 GDC 에어 갭 Kubernetes 클러스터 내에서 데모 컨테이너화된 애플리케이션(hello-app)을 배포하는 방법을 자세히 설명합니다. 필요한 Kubernetes 배포 및 서비스 리소스를 만들어 hello-app을 실행하고 클러스터 내에서 내부적으로 노출하여 L7 부하 분산기를 사용하여 액세스할 수 있도록 준비합니다.

  1. 데모 컨테이너화된 앱의 샘플 이미지를 Harbor에 업로드합니다.

    docker pull gcr.io/google-samples/hello-app:1.0 \
      --platform linux/amd64
    docker tag gcr.io/google-samples/hello-app:1.0 \
      ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/hello-app:1.0
    docker --config=./docker push \
      ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/hello-app:1.0
    
  2. 표준 클러스터에서 다음 매니페스트를 배포합니다.

    cat << EOF > hello-app.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-app
      namespace: hello-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: hello-app
      template:
        metadata:
          labels:
            app: hello-app
        spec:
          containers:
          - name: hello-server
            image: ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/hello-app:1.0
            ports:
            - containerPort: 8080
          imagePullSecrets:
          - name: ${IMAGE_PULL_SECRET_NAME}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-app
      namespace: hello-app
    spec:
      type: ClusterIP
      selector:
        app: hello-app
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
    EOF
    
    kk apply -f hello-app.yaml
    

그런 다음 배포 및 서비스가 있는지 확인합니다.

kk get svc,deploy -n hello-app

VM에 데모 앱 배포

이 섹션에서는 Kubernetes 클러스터 외부의 가상 머신 (VM) 내에서 데모 애플리케이션을 배포하는 방법을 자세히 설명합니다. VM에 HTTP 서버를 설정하면 부하 분산기가 노출할 수 있는 외부 애플리케이션을 시뮬레이션하여 클러스터 내부 및 외부의 리소스에 대한 트래픽을 관리하는 기능을 보여줍니다.

먼저 데모 앱의 VM을 만듭니다.

  1. 웹브라우저에서 GDC 콘솔을 엽니다.
  2. 표준 Kubernetes 클러스터를 만든 프로젝트와 동일한 프로젝트를 선택합니다.
  3. 메뉴를 열고 가상 머신 을 클릭합니다.
  4. 인스턴스 만들기 를 클릭합니다.
  5. VM 이름을 vm-workload로 지정합니다. 이 예시에서는 vCPU가 2개인 이미지가 충분합니다.
  6. 부팅 디스크 이미지의 경우 Python이 사전 설치된 Ubuntu 22.04 배포를 선택합니다.
  7. 만들기 를 클릭합니다.
  8. VM이 준비될 때까지 몇 분 정도 기다립니다.
  9. VM에 SSH 연결을 설정합니다.
    1. GDC 콘솔에서 VM을 클릭합니다.
    2. SSH로 연결 을 클릭합니다.

SSH 콘솔에 연결한 후 다음을 실행합니다.

mkdir ~/simple-server
cd ~/simple-server
echo 'Welcome to my VM!' > index.html
python3 -m http.server --bind 0.0.0.0 8080 &

VM으로 트래픽을 라우팅하려면 선택자가 없는 헤드리스 서비스를 만듭니다. 이는 EndpointSlice 리소스를 사용하여 VM의 내부 IP 주소에 수동으로 매핑됩니다.

kk apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: vm-app-svc
  namespace: vm-app
spec:
  ports:
  - protocol: TCP
    port: 443
    targetPort: 443
EOF

다음을 실행하여 vm-workload VM의 IP 주소를 가져옵니다.

gdcloud compute instances list --project ${PROJECT_ID} \
  | grep workload-vm | awk '{print $3}'

출력은 EndpointSlice 리소스를 설정하는 데 필요한 VM의 IP 주소입니다.

VM-app 선택자가 없는 서비스에 연결하고 트래픽이 라우팅되어야 하는 VM의 IP 주소를 지정하는 리소스 EndpointSlice를 만듭니다.

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: vm-app-endpoints
  namespace: vm-app
  labels:
    kubernetes.io/service-name: vm-app-svc
addressType: IPv4
ports:
  - port: 8080
endpoints:
  - addresses:
    - "VM_IP"
    conditions:
      ready: true

자체 서명 인증서 만들기

이 섹션에서는 컨테이너 기반 및 VM 기반 애플리케이션의 통신을 보호하기 위해 TLS 인증서와 Kubernetes 보안 비밀을 만드는 프로세스를 안내합니다. 이 가이드에서는 편의를 위해 자체 서명 인증서를 사용하지만 프로덕션 환경에서는 선택사항: 프로덕션 준비 인증서 사용에 설명된 대로 프로덕션 등급 인증서를 사용해야 합니다. 이러한 앱의 임의 샘플 도메인 이름을 선택합니다. 보안 연결을 설정하면 HAProxy 인그레스 컨트롤러를 통해 애플리케이션에 액세스하는 클라이언트의 데이터 무결성과 기밀성을 보장할 수 있습니다.

컨테이너화된 앱의 경우 자체 서명 인증서를 만들고 부하 분산기 네임스페이스에 보안 비밀로 저장합니다. 이는 k8s-app.example.com을 요청할 때 TLS에 사용됩니다.

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout tls-containerized.key \
  -out tls-containerized.crt \
  -subj "/CN=k8s-app.example.com" \
  -days 365

kk create secret tls tls-containerized \
  --namespace load-balancer \
  --key tls-containerized.key \
  --cert tls-containerized.crt

kk create secret tls tls-containerized \
  --namespace hello-app \
  --key tls-containerized.key \
  --cert tls-containerized.crt

VM 앱의 경우 유사한 자체 서명 인증서가 발급되고 저장됩니다. 이는 vm-app.example.com을 요청할 때 TLS에 사용됩니다.

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout tls-vm.key \
  -out tls-vm.crt \
  -subj "/CN=vm-app.example.com" \
  -days 365

kk create secret tls tls-vm \
  --namespace load-balancer \
  --key tls-vm.key \
  --cert tls-vm.crt

kk create secret tls tls-vm \
  --namespace vm-app \
  --key tls-vm.key \
  --cert tls-vm.crt

HAProxy 배포

HAProxy 인그레스 컨트롤러 및 L4 LB 설치

export HAPROXY_VERSION=3.1.14

# pull the HAProxy Ingress Controller image and push it to Harbor
docker pull haproxytech/kubernetes-ingress:${HAPROXY_VERSION} \
  --platform linux/amd64
docker tag haproxytech/kubernetes-ingress:${HAPROXY_VERSION} \
  ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/haproxy-ingress:${HAPROXY_VERSION}
docker --config=./docker push \
  ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/haproxy-ingress:${HAPROXY_VERSION}

# Get Helm repo
helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update

# Install the Ingress Controller with helm
helm upgrade --install haproxy-kubernetes-ingress \
  haproxytech/kubernetes-ingress \
  --kubeconfig kubeconfig-${CLUSTER_NAME}.yaml \
  --namespace load-balancer \
  --set controller.image.repository=${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/haproxy-ingress \
  --set controller.image.tag=${HAPROXY_VERSION} \
  --set controller.existingImagePullSecret=${IMAGE_PULL_SECRET_NAME} \
  --set controller.service.type=LoadBalancer \
  --set-json \
  controller.service.annotations='{"networking.gke.io/load-balancer-type": "internal"}'

HAProxy 인그레스 컨트롤러는 LoadBalancer 유형 서비스를 사용하여 클라이언트 액세스를 위한 고유한 가상 IP 주소를 가져옵니다. 이 서비스는 완전 관리형 레이어 4 부하 분산기를 설정합니다. 이 가이드의 단순성을 위해 load-balancer-type 주석을 internal로 설정하여 내부 부하 분산기를 만듭니다. 이 주석을 생략하면 외부 부하 분산기가 생성됩니다. Kubernetes 배포는 Harbor 로봇 계정의 사용자 인증 정보가 포함된 제공된 보안 비밀(${IMAGE_PULL_SECRET_NAME})을 사용하여 Harbor에서 이미지를 안전하게 가져옵니다.

HAProxy 인그레스 컨트롤러 설치 확인

HAProxy 인그레스 컨트롤러의 포드가 실행 중이고 준비되었는지 확인합니다.

kk get pods -n load-balancer

다음과 유사하게 출력됩니다.

NAME                                          READY   STATUS      RESTARTS   AGE
haproxy-kubernetes-ingress-78dc9c8676-f8fcb   1/1     Running     0          35s
haproxy-kubernetes-ingress-78dc9c8676-lfnr2   1/1     Running     0          65s
haproxy-kubernetes-ingress-crdjob-3-tgj2h     0/1     Completed   0          65s

HAProxy 인그레스 컨트롤러의 서비스가 생성되고 구성되었는지 확인합니다.

kk get services -n load-balancer

결과는 다음과 유사합니다.

NAME                         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                  AGE
haproxy-kubernetes-ingress   LoadBalancer   10.252.27.46   10.252.4.17   80:32023/TCP,443:31103/TCP,443:31103/UDP,1024:30146/TCP,6060:30718/TCP   10m

데모 앱의 인그레스 리소스 정의

HAProxy를 컨테이너화된 앱 서비스에 연결하는 인그레스 리소스를 만듭니다.

cat << EOF > hello-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-app-ingress
  namespace: hello-app
  annotations:
    haproxy.org/ssl-redirect: "true"
    haproxy.org/ssl-redirect-port: "443"
    haproxy.org/ssl-redirect-code: "308"
spec:
  ingressClassName: haproxy
  tls:
  - hosts:
    - "k8s-app.example.com"
    secretName: tls-containerized
  rules:
  - host: "k8s-app.example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello-app
            port:
              number: 80
EOF

kk apply -f hello-app-ingress.yaml

VM-app 선택자가 없는 서비스에 연결하고 트래픽이 라우팅되어야 하는 VM의 IP 주소를 지정하는 인그레스 리소스를 만듭니다.

cat << EOF > vm-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: vm-app-ingress
  namespace: vm-app
  annotations:
    haproxy.org/ssl-redirect: "true"
    haproxy.org/ssl-redirect-port: "443"
    haproxy.org/ssl-redirect-code: "308"
spec:
  ingressClassName: haproxy
  tls:
  - hosts:
    - "vm-app.example.com"
    secretName: tls-vm
  rules:
  - host: "vm-app.example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: vm-app-svc
            port:
              number: 443
EOF

kk apply -f vm-ingress.yaml

부하 분산기 IP 주소 가져오기

명령어를 실행하여 부하 분산기의 IP 주소를 가져옵니다.

kk get services/haproxy-kubernetes-ingress \
  -n load-balancer \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

이는 앱에 대한 액세스를 확인할 때 필요합니다. 이를 LOAD_BALANCER_IP라고 합니다.

클라이언트 VM 만들기

다음 단계에 따라 클라이언트 VM을 만듭니다.

  1. 웹브라우저에서 GDC 콘솔을 엽니다.
  2. 메뉴를 열고 가상 머신 을 클릭합니다.
  3. 인스턴스 만들기 를 클릭합니다.
  4. client라는 VM을 만들고, 소형 머신 유형을 선택하고, curl이 사전 설치된 Rocky Linux 또는 Ubuntu를 선택합니다.
  5. 만들기 를 클릭합니다.
  6. VM이 준비될 때까지 몇 분 정도 기다립니다.
  7. VM이 준비되면 VM과 SSH 연결을 설정합니다.
    1. GDC 콘솔에서 VM을 클릭합니다.
    2. SSH로 연결 을 클릭합니다.

액세스 및 라우팅 확인

라우팅을 테스트하려면 클라이언트 VM에서 curl 명령어를 실행합니다. 부하 분산기의 IP 주소를 사용하여 정의된 호스트 이름으로 두 애플리케이션에 모두 연결할 수 있습니다.

curl에서 --resolve 플래그를 전달하면 도메인 이름이 GDC 에어 갭 L4 부하 분산기의 IP로 확인되도록 강제할 수 있습니다. 자체 서명 인증서를 신뢰하기 위해 -k 플래그를 전달합니다.

Kubernetes 컨테이너화된 앱을 테스트합니다.

curl -k --resolve k8s-app.example.com:443:$LOAD_BALANCER_IP https://k8s-app.example.com -v

외부 VM 앱을 테스트합니다.

curl -k --resolve vm-app.example.com:443:$LOAD_BALANCER_IP https://vm-app.example.com -v

올바르게 구성된 경우 인그레스 컨트롤러는 TLS 종료자로 원활하게 작동하고 트래픽을 대상으로 패스 스루합니다.

선택사항: 프로덕션 준비 인증서 사용

이 섹션에서는 GDC 에어 갭 CA 서비스를 활용하여 비공개 루트 인증 기관 (CA)을 만들고, 워크로드에 서명된 인증서를 발급하고, GDC 에어 갭 표준 클러스터 및 클라이언트 VM을 안전하게 업데이트하는 방법을 설명합니다.

이 섹션에서는 GDC 에어 갭 CA Service를 사용하여 비공개 루트 인증 기관 (CA)을 만들고 애플리케이션에 유효한 인증서를 발급하는 방법을 설명합니다. 클라이언트 VM에 이 루트 CA를 설치하면 SSL 경고를 우회하지 않고도 (예: curl -k 사용) TLS 종료가 신뢰할 수 있는 인증서와 원활하게 작동하는지 확인할 수 있습니다.

필요한 권한 부여 및 사용자 인증 정보 가져오기

CA 서비스를 관리하고 인증서를 발급하려면 사용자에게 프로젝트의 적절한 IAM 역할이 필요합니다.

  1. certificate-authority-service-admincertificate-requester 역할을 부여합니다.

    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member=user:${USER_NAME} \
      --role=certificate-authority-service-admin
    
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member=user:${USER_NAME} \
      --role=certificate-requester
    
  2. 관리 API 서버의 사용자 인증 정보를 가져옵니다.

    gdcloud clusters get-credentials ${ORG_NAME}-admin
    

루트 CA 만들기

프로젝트 네임스페이스 내의 관리 API 서버에서 인증 기관을 만듭니다.

  1. CertificateAuthority 리소스를 적용합니다.

    km apply -f - <<EOF
    apiVersion: pki.security.gdc.goog/v1
    kind: CertificateAuthority
    metadata:
      name: my-root-ca
      namespace: ${PROJECT_ID}
    spec:
      caProfile:
        commonName: "My Root CA"
        duration: 87600h # 10 years
        keyAlgorithm: RSA_2048
        maxChainLength: 1
      caType: ROOT
      keyLocation: HSM
      rotationPolicy:
        cronTime: 0 0 1 1 *
    EOF
    
    km -n ${PROJECT_ID} get \
    certificateauthority.pki.security.gdc.goog/my-root-ca -ojson \
    | jq -r '
    .status.conditions[] | select( .type as $id | "Ready" | index($id)) .status'
    

인증서 발급 및 배포

CA가 준비되면 컨테이너화된 앱과 VM 기반 앱 모두에 대한 인증서를 요청합니다. 이러한 요청은 관리 API 서버에서 발생하며 결과 키는 표준 클러스터로 이동해야 합니다.

두 도메인 모두에 대한 요청을 만듭니다.

km apply -f - <<EOF
apiVersion: pki.security.gdc.goog/v1
kind: CertificateRequest
metadata:
  name: tls-containerized-req
  namespace: ${PROJECT_ID}
spec:
  certificateAuthorityRef:
    name: my-root-ca
    namespace: ${PROJECT_ID}
  certificateConfig:
    subjectConfig:
      commonName: "k8s-app.example.com"
      dnsNames:
      - "k8s-app.example.com"
  signedCertificateSecret: tls-containerized-signed
---
apiVersion: pki.security.gdc.goog/v1
kind: CertificateRequest
metadata:
  name: tls-vm-req
  namespace: ${PROJECT_ID}
spec:
  certificateAuthorityRef:
    name: my-root-ca
    namespace: ${PROJECT_ID}
  certificateConfig:
    subjectConfig:
      commonName: "vm-app.example.com"
      dnsNames:
      - "vm-app.example.com"
  signedCertificateSecret: tls-vm-signed
EOF

인증서가 발급될 때까지 잠시 기다립니다. 준비 상태가 True이면 준비되었는지 확인할 수 있습니다.

km get certificaterequests -n ${PROJECT_ID}

표준 클러스터 업데이트

이 가이드의 이전 섹션을 따른 경우 표준 클러스터에 자체 서명된 보안 비밀이 있습니다. 새 서명된 버전을 만들기 전에 삭제해야 합니다.

kk delete secret tls-containerized -n load-balancer
kk delete secret tls-vm -n load-balancer

kk delete secret tls-containerized -n hello-app
kk delete secret tls-vm -n vm-app

이제 관리 API 서버에서 서명된 인증서를 추출하고 표준 클러스터에서 새 보안 비밀을 만듭니다.

km get secret -n ${PROJECT_ID} tls-containerized-signed \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d > tls-containerized.crt

km get secret -n ${PROJECT_ID} tls-containerized-signed \
  -o jsonpath='{.data.tls\.key}' \
  | base64 -d > tls-containerized.key

kk create secret tls tls-containerized \
  --namespace load-balancer \
  --key tls-containerized.key \
  --cert tls-containerized.crt

kk create secret tls tls-containerized \
  --namespace hello-app \
  --key tls-containerized.key \
  --cert tls-containerized.crt

km get secret -n ${PROJECT_ID} tls-vm-signed \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d > tls-vm.crt

km get secret -n ${PROJECT_ID} tls-vm-signed \
  -o jsonpath='{.data.tls\.key}' \
  | base64 -d > tls-vm.key

kk create secret tls tls-vm \
  --namespace load-balancer \
  --key tls-vm.key \
  --cert tls-vm.crt

kk create secret tls tls-vm \
  --namespace vm-app \
  --key tls-vm.key \
  --cert tls-vm.crt

새 보안 비밀은 부하 분산기에 의해 자동으로 가져오고 새로고침됩니다.

클라이언트 신뢰 구성

설정을 확인하려면 클라이언트 VM이 새 루트 CA를 신뢰하도록 해야 합니다.

루트 CA 인증서를 파일로 추출합니다.

km get secret -n ${PROJECT_ID} my-root-ca-secret \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d > my-root-ca.crt

인증서를 클라이언트 VM으로 전송합니다. (my-root-ca.crt의 콘텐츠를 복사하여 클라이언트 VM의 파일에 붙여넣을 수 있습니다.)

클라이언트 VM에서 트러스트 저장소를 업데이트합니다.

client VM이 Ubuntu인 경우:

sudo cp my-root-ca.crt /usr/local/share/ca-certificates/
sudo chmod 644 /usr/local/share/ca-certificates/my-root-ca.crt
sudo update-ca-certificates

client VM이 Rocky Linux인 경우:

sudo cp my-root-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

액세스 확인

이제 -k 플래그 없이 curl을 사용하여 애플리케이션에 액세스할 수 있습니다. 연결은 완전히 신뢰할 수 있습니다.

k8s 컨테이너화된 앱을 테스트합니다.

curl -v --resolve k8s-app.example.com:443:LOAD_BALANDER_IP https://k8s-app.example.com

VM 앱을 테스트합니다.

curl -v --resolve vm.example.com:443:LOAD_BALANDER_IP https://vm-app.example.com

성공하면 SSL 인증서 경고 없이 애플리케이션 출력이 즉시 표시됩니다.