개발자를 위한 Kubernetes DNS 및 인증서 관리 방안
이번 포스트에서는 Kubernetes DNS 를 관리하는 방법에 대해 자세히 다룰 것입니다.
애플리케이션 개발의 궁극적인 목표는 인터넷에 애플리케이션을 노출하는 것입니다. 개발자의 경우 Kubernetes는 요청을 애플리케이션으로 라우팅하기 위한 메커니즘으로 Ingress Controller를 사용하여 프로세스를 어느 정도 단순화합니다. 그러나 모든 것이 원하는 만큼 Self-Service인 것은 아닙니다. 애플리케이션의 도메인 이름을 Ingress Controller의 IP 주소에 매핑하기 위해 DNS(Domain Name System)에 레코드가 필요하고 HTTPS를 사용하여 연결을 보호하기 위해 TLS 인증서가 필요합니다. 대부분의 조직에서는 DNS 또는 TLS를 소유하지 않으므로 이를 수행하는 운영 그룹과 조정해야 합니다.
운영자에게 상황이 반드시 더 쉬운 것은 아닙니다. 대부분의 조직에서 DNS 레코드를 업데이트해야 할 필요성은 매우 드물기 때문에 비즈니스 규칙과 실제 기술 단계 모두에서 절차가 드물거나 존재하지 않는 경향이 있습니다. 즉, DNS 레코드를 추가해야 할 때 먼저 문서를 찾거나 동료에게 요청하거나 (최악의 경우) 스스로 알아내야 합니다. 또한 회사 보안 규칙을 준수하고 있는지 확인하고 Ingress 방화벽에 대한 입력 태그가 올바르게 지정되었는지 확인해야 합니다.
다행스럽게도 개발자와 운영자 모두의 삶을 더 쉽게 만들 수 있는 방법이 있습니다. 이 포스트에서는 개발자가 Kubernetes 환경에서 DNS 레코드를 업데이트하고 TLS 인증서를 생성할 수 있도록 Self-Service를 활성화하도록 운영자가 Kubernetes 배포를 구성하는 방법을 보여줍니다. 미리 인프라를 구축함으로써 필요한 모든 비즈니스 및 기술 요구 사항을 모두 충족할 수 있습니다.
목차
1. 개요 및 전제 조건
2. 솔루션 배포
2-1. 소프트웨어 다운로드
2-2. NGINX Ingress Controller 배포
2-3. Cert-Manager 배포
2-4. Kubernetes DNS – ExternalDNS 배포
2-5. Kubernetes DNS – Sample 애플리케이션 배포
2-6. Kubernetes DNS – 솔루션 검증
3. 개발자가 NGINX Ingress Controller를 배포할 때 발생하는 동작
4. Troubleshooting
1. 개요 및 전제 조건
솔루션이 준비되면 개발자가 애플리케이션을 인터넷에 노출하기 위해 Kubernetes 설치로 관리되는 도메인 내에서 FQDN(정규화된 도메인 이름)을 포함하는 제공된 템플릿을 따라 Ingress Controller를 생성하기만 하면 됩니다. Kubernetes는 템플릿을 사용하여 Ingress Controller에 대한 IP 주소를 할당하고 DNS A 레코드를 생성하여 FQDN을 IP 주소에 매핑하고, FQDN에 대한 TLS 인증서를 생성하여 Ingress Controller에 추가합니다. 정리도 마찬가지로 쉽습니다. Ingress가 제거되면 DNS 레코드도 정리됩니다.
이 솔루션은 다음 기술을 활용합니다(아래에 설치 및 구성 지침 제공).
- Kubernetes.
- ExternalDNS.
- cert-manager.
- Let’s Encrypt.
- NGINX Open Source 또는 NGINX Plus를 기반으로 하는 NGINX의 NGINX Ingress Controller, 이 솔루션은 Kubernetes 커뮤니티에서 유지 관리하는 NGINX Ingress Controller에서 작동하지 않습니다. 두 프로젝트의 차이점에 대한 자세한 내용은 포스트를 참조하세요.
솔루션을 구성하기 전에 다음이 필요합니다.
- Egress(LoadBalancer) 개체가 있는 Kubernetes 클라우드 설치, 이 솔루션은 Linode를 사용하지만 다른 클라우드 공급자도 작동합니다.
- cert-manager에 대해 지원되는 DNS 공급자 중 하나이고 ExternalDNS를 지원하기 때문에 선택한 Cloudflare와 함께 호스팅 되는 도메인 이름(작성 당시 베타 버전). Production 또는 기타 중요한 목적으로 도메인을 사용하지 않는 것이 좋습니다.
- Free Tier에 포함된 Cloudflare API에 액세스할 수 있습니다.
- Kubernetes 설치 및 배포를 위한 Helm.
- Kubectl은 Kubernetes의 Command‑Line 인터페이스로 사용됩니다.
- 선택 옵션으로, Kubernetes와 상호 작용하는 보다 구조화된 방법을 제공하는 잘 구성된 유형의 사용자 인터페이스(TUI)인 K9s.
또한 Kubernetes에 대한 기본적인 이해가 있다고 가정합니다(Manifest를 적용하고, Helm 차트를 사용하고, kubectl 명령을 실행하여 출력을 보고 문제를 해결하는 방법). Let’s Encrypt의 기본 개념을 이해하는 것이 도움이 되지만 필수는 아닙니다. 개요는 포스트를 참조하십시오. 또한 cert-manager가 어떻게 작동하는지 알 필요는 없지만, cert-manager(및 일반적으로 인증서)가 NGINX Ingress Controller와 작동하는 방식에 관심이 있다면 최근 포스트인 Kubernetes에서 인증서 관리 자동화를 참조하세요.
macOS와 Linux 모두에서 테스트 되었습니다. WSL2(Windows Subsystem for Linux version 2)용 Windows 하위 시스템에서는 테스트하지 않았지만 문제가 발생할 것으로 예상되지 않습니다.
Note: 이 솔루션은 Production 용도가 아니라 샘플 개념 증명용입니다. 특히 운영 및 보안에 대한 모든 모범 사례를 통합하지는 않습니다. 이러한 항목에 대한 자세한 내용은 cert-manager 및 ExternalDNS 설명서를 참조하세요.
2. Kubernetes DNS 및 인증서 관리를 위한 솔루션 배포
솔루션을 배포하려면 다음 섹션의 단계를 따르세요.
2-1. 소프트웨어 다운로드
1. Cloudflare API Token을 다운로드하세요.
2. NGINX Ingress Controller Repository를 Clone합니다.
$ git clone https://github.com/nginxinc/kubernetes-ingress.git
Cloning into 'kubernetes-ingress'...
remote: Enumerating objects: 45176, done.
remote: Counting objects: 100% (373/373), done.
remote: Compressing objects: 100% (274/274), done.
remote: Total 45176 (delta 173), reused 219 (delta 79), pack-reused 44803
Receiving objects: 100% (45176/45176), 60.45 MiB | 26.81 MiB/s, done.
Resolving deltas: 100% (26592/26592), done.
3. Kubernetes 클러스터에 연결할 수 있는지 확인합니다.
$ kubectl cluster-info
Kubernetes control plane is running at https://ba35bacf-b072-4600-9a04-e04...6a3d.us-west-2.linodelke.net:443
KubeDNS is running at https://ba35bacf-b072-4600-9a04-e04...6a3d.us-west-2.linodelke.net:443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
2-2. NGINX Ingress Controller 배포
1. Helm을 사용하여 NGINX Ingress Controller를 배포합니다. 세 가지 비표준 구성 옵션이 추가됩니다.
- controller.enableCustomResources – NGINX VirtualServer 및 VirtualServerRoute 사용자 지정 리소스를 생성하는 데 사용되는 CRD(Custom Resource Definition)를 설치하도록 Helm에 지시합니다.
- controller.enableCertManager – Cert-Manager 구성 요소와 통신하도록 NGINX Ingress Controller를 구성합니다.
- controller.enableExternalDNS – ExternalDNS 구성 요소와 통신하도록 Ingress Controller를 구성합니다.
$ helm install nginx-kic nginx-stable/nginx-ingress --namespace nginx-ingress --set controller.enableCustomResources=true --create-namespace --set controller.enableCertManager=true --set controller.enableExternalDNS=true
NAME: nginx-kic
LAST DEPLOYED: Day Mon DD hh:mm:ss YYYY
NAMESPACE: nginx-ingress
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The NGINX Ingress Controller has been installed.
2. NGINX Ingress Controller가 실행 중인지 확인하고 EXTERNAL-IP 필드의 값을 기록해 둡니다. NGINX Ingress Controller의 IP 주소(여기서는 http://www.xxx.yyy.zzz)입니다. 출력은 가독성을 위해 두 줄에 걸쳐 표시됩니다.
$ kubectl get services --namespace nginx-ingress
NAME TYPE CLUSTER-IP ...
nginx-kic-nginx-ingress LoadBalancer 10.128.152.88 ...
... EXTERNAL-IP PORT(S) AGE
... www.xxx.yyy.zzz 80:32457/TCP,443:31971/TCP 3h8m
2-3. Cert-Manager 배포
이 솔루션에서 cert-manager는 TLS 인증서를 얻을 때 DNS-01 Challenge 유형을 사용합니다. 이를 위해서는 ClusterIssuer 리소스를 생성하는 동안 Cloudflare API Token을 제공해야 합니다. 솔루션에서 API Token은 Kubernetes Secret으로 제공됩니다.
1. Helm을 사용하여 cert-manager를 배포합니다.
$ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.9.1 --set installCRDs=true
NAME: cert-manager
LAST DEPLOYED: Day Mon DD hh:mm:ss YYYY
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.9.1 has been deployed successfully!
2. Cloudflare API Token을 Kubernetes Secret로 배포하고 <your-API-token>을 대체합니다.
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: Cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: "<your-API-token>"
EOF
secret/Cloudflare-api-token-secret created
3. Token을 검색할 위치로 Cloudflare-api-token-secret(이전 단계에서 정의됨)를 지정하여 ClusterIssuer 개체를 만듭니다. 원하는 경우 metadata.name 필드의 example-issuer(및 spec.acme.privateKeySecretRef.name 필드의 example-issuer-account-key)를 다른 이름으로 바꿀 수 있습니다.
$ kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: example-issuer
namespace: cert-manager
spec:
acme:
email: example@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: example-issuer-account-key
solvers:
- dns01:
Cloudflare:
apiTokenSecretRef:
name: Cloudflare-api-token-secret
key: api-token
EOF
clusterissuer.cert-manager.io/example-issuer created
4. ClusterIssuer가 배포되고 준비되었는지 확인합니다(READY 필드의 값이 True임).
$ kubectl get clusterissuer
NAME READY AGE
example-issuer True 3h9m
2-4. Kubernetes DNS – ExternalDNS 배포
cert-manager와 마찬가지로, ExternalDNS 프로젝트는 DNS를 관리하기 위해 Cloudflare API Token이 필요합니다. 두 프로젝트에 동일한 Token을 사용할 수 있지만 필수는 아닙니다.
1. NGINX Ingress Controller용 ExternalDNS CRD를 생성하여 프로젝트 간 통합을 활성화합니다.
$ kubectl create -f ./kubernetes-ingress/deployments/common/crds/externaldns.nginx.org_dnsendpoints.yaml
customresourcedefinition.apiextensions.k8s.io/dnsendpoints.externaldns.nginx.org created
2. ExternalDNS 서비스(external-dns)를 생성합니다. Manifest가 다소 길기 때문에 여기서는 Manifest를 두 부분으로 나눕니다. 첫 번째 부분은 계정, 역할 및 사용 권한을 구성합니다.
- DNS 관리를 위한 모든 쓰기 및 업데이트 작업을 관리하기 위해 external-dns라는 ServiceAccount 개체를 만듭니다.
- 필요한 권한을 정의하는 ClusterRole 개체(external-dns라고도 함)를 만듭니다.
- ClusterRole을 ServiceAccount에 Bind합니다.
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: ["externaldns.nginx.org"]
resources: ["dnsendpoints"]
verbs: ["get","watch","list"]
- apiGroups: ["externaldns.nginx.org"]
resources: ["dnsendpoints/status"]
verbs: ["update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
EOF
serviceaccount/external-dns created
clusterrole.rbac.authorization.k8s.io/external-dns created
clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
Manifest의 두 번째 부분은 ExternalDNS 배포를 생성합니다.
- ExternalDNS가 도메인을 관리할 때 발생할 수 있는 손상 범위를 제한하는 도메인 필터를 생성합니다. 예를 들어 Production 환경의 변경을 방지하기 위해 스테이징(Staging) 환경의 도메인 이름을 지정할 수 있습니다. 이 예에서는 domain-filter를 example.com으로 설정합니다.
- CF_API_TOKEN 환경 변수를 Cloudflare API Token으로 설정합니다. <your-API-token>의 경우 실제 Token 또는 Token이 포함된 Secret로 대체합니다. 후자의 경우 환경 변수를 사용하여 보안 Secret을 컨테이너에 프로젝션 해야 합니다.
- FREE_TIER 환경 변수를 “true”로 설정합니다(유료 Cloudflare 구독이 없는 경우 적합).
$ kubectl apply -f - <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.12.0
args:
- --source=service
- --source=ingress
- --source=crd
- --crd-source-apiversion=externaldns.nginx.org/v1
- --crd-source-kind=DNSEndpoint
- --domain-filter=example.com
- --provider=Cloudflare
env:
- name: CF_API_TOKEN
value: "<your-API-token>"
- name: FREE_TIER
value: "true"
EOF
serviceaccount/external-dns created
clusterrole.rbac.authorization.k8s.io/external-dns created
clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
deployment.apps/external-dns created
2-5. Kubernetes DNS – Sample 애플리케이션 배포
테스트 목적으로 Cafe라는 표준 NGINX Ingress Controller Sample 애플리케이션을 사용하십시오.
1. Cafe 애플리케이션을 배포합니다.
$ kubectl apply -f ./kubernetes-ingress/examples/ingress-resources/complete-example/cafe.yaml
deployment.apps/coffee created
service/coffee-svc created
deployment.apps/tea created
service/tea-svc created
2. Cafe 애플리케이션에 NGINX Ingress Controller를 배포합니다. 다음 설정에 유의하세요.
- kind: VirtualServer – 표준 Kubernetes Ingress 리소스가 아닌 NGINX VirtualServer 사용자 정의 리소스를 사용하고 있습니다.
- spec.host – cafe.example.com을 배포 중인 호스트명으로 바꿉니다. 호스트는 ExternalDNS로 관리되는 도메인 내에 있어야 합니다.
- spec.tls.cert-manager.cluster-issuer – 이 포스트에 지정된 값을 사용했다면 이는 example-issuer입니다. 필요한 경우 Cert-Manager 배포의 3단계에서 선택한 이름을 대체합니다.
- spec.externalDNS.enable – true 값은 ExternalDNS에 DNS A 레코드를 생성하도록 지시합니다.
Kubernetes가 공급자의 DNS API와 상호 작용하므로 이 단계를 완료하는 데 걸리는 시간은 DNS 공급자에 따라 크게 달라집니다.
$ kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: cafe
spec:
host: cafe.example.com
tls:
secret: cafe-secret
cert-manager:
cluster-issuer: example-issuer
externalDNS:
enable: true
upstreams:
- name: tea
service: tea-svc
port: 80
- name: coffee
service: coffee-svc
port: 80
routes:
- path: /tea
action:
pass: tea
- path: /coffee
action:
pass: coffee
EOF
virtualserver.k8s.nginx.org/cafe created
2-6. Kubernetes DNS – 솔루션 검증
1. DNS A 레코드를 확인합니다. 특히 ANSWER SECTION 블록에서 FQDN(여기서는 cafe.example.com)이 올바른 IP 주소(www.xxx.yyy.zzz)에 매핑되어 있는지 확인합니다.
$ dig cafe.example.com
; <<>> DiG 9.10.6 <<>> cafe.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22633
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;cafe.example.com. IN A
;; ANSWER SECTION:
cafe.example.com. 279 IN A www.xxx.yyy.zzz
;; Query time: 1 msec
;; SERVER: 2607:fb91:119b:4ac4:2e0:xxxx:fe1e:1359#53(2607:fb91:119b:4ac4:2e0:xxxx:fe1e:1359)
;; WHEN: Day Mon DD hh:mm:ss TZ YYYY
;; MSG SIZE rcvd: 67
2. 인증서가 유효한지 확인하십시오(READY 필드의 값이 True임).
$ kubectl get certificates
NAME READY SECRET AGE
cafe-secret True cafe-secret 8m51s
3. 애플리케이션에 연결할 수 있는지 확인합니다.
$ curl https://cafe.example.com/coffee
Server address: 10.2.2.4:8080
Server name: coffee-7c86d7d67c-lsfs6
Date: DD/Mon/YYYY:hh:mm:ss +TZ-offset
URI: /coffee
Request ID: 91077575f19e6e735a91b9d06e9684cd
$ curl https://cafe.example.com/tea
Server address: 10.2.2.5:8080
Server name: tea-5c457db9-ztpns
Date: DD/Mon/YYYY:hh:mm:ss +TZ-offset
URI: /tea
Request ID: 2164c245a495d22c11e900aa0103b00f
3. 개발자가 NGINX Ingress Controller를 배포할 때 발생하는 동작
솔루션이 마련되면 내부적으로 많은 일이 발생합니다. 다이어그램은 개발자가 NGINX VirtualServer 사용자 정의 리소스와 함께 NGINX Ingress Controller를 배포할 때 발생하는 상황을 보여줍니다. 일부 운영 세부 정보가 생략되었습니다.

1. 개발자는 NGINX CRD를 사용하여 VirtualServer 리소스를 배포합니다.
2. Kubernetes는 NGINX Ingress Controller를 사용하여 VirtualServer를 생성합니다.
3. NGINX Ingress Controller는 ExternalDNS를 호출하여 DNS A 레코드를 생성합니다.
4. ExternalDNS는 DNS에 A 레코드를 생성합니다.
5. NGINX Ingress Controller는 Cert-Manager를 호출하여 TLS 인증서를 요청합니다.
6. Cert-Manager는 DNS-01 Challenge 중에 사용할 DNS 레코드를 추가합니다.
7. Cert-Manager가 Let’s Encrypt에 연락하여 Challenge를 완료합니다.
8. Let’s Encrypt는 DNS에 대한 Challenge를 검증합니다.
9. Let’s Encrypt에서 TLS 인증서를 발급합니다.
10. Crt-Manager는 NGINX Ingress Controller에 TLS 인증서를 제공합니다.
11. NGINX Ingress Controller는 TLS 보안 외부 요청을 애플리케이션 Pod로 라우팅합니다.
4. Troubleshooting
우리가 사용하고 있는 구성 요소와 함께 Kubernetes의 복잡성을 고려할 때 포괄적인 문제 해결 가이드를 제공하기는 어렵습니다. 그럼에도 문제를 파악하는 데 도움이 되는 몇 가지 기본 제안이 있습니다.
kubectl get
및kubectl describe
명령을 사용하여 배포된 개체의 구성을 검증합니다.kubectl logs
<component> 명령을 사용하여 배포된 다양한 구성 요소에 대한 로그 파일을 봅니다.- K9s를 사용하여 설치를 검사하십시오. 소프트웨어에는 문제의 심각도에 따라 노란색 또는 빨간색으로 강조 표시하고 개체에 대한 로그 및 세부 정보에 액세스할 수 있는 인터페이스를 제공합니다.
NGINX Plus 기반의 NGINX Ingress Controller를 사용해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.