✔ 테스트 환경
Kubernetes 환경
- OS: Windows 10
- WSL2 (Ubuntu 24.04)
- Docker Desktop (WSL2 backend 사용)
- kind 클러스터
네트워크 / LoadBalancer 환경
- kind는 LoadBalancer 타입 Service를 기본 제공하지 않음
- Metallb를 설치하여 LoadBalancer 기능 대체
✔ DNS Provider 및 ExternalDNS 정보
DNS Provider: AWS Route53
- Public Hosted Zone 사용
- Public Domain : lake-devops.click
- ExternalDNS가 Route53 API를 호출하여 DNS 레코드 자동 생성
ExternalDNS 인증 방식
- IAM AccessKey 기반 인증
- Credentials 파일을 Kubernetes Secret으로 저장
ExternalDNS 설치 정보
- Helm chart: kubernetes-sigs/external-dns
- Provider: AWS
- Domain Filter: lake-devops.click
- Registry: TXT (충돌 방지용 OwnerID 관리)
✔ 테스트 실행 순서
- ExternalDNS, Ingress Controller, Metallb 설치
- Sample Service or Ingress 배포
- ExternalDNS 로그에서 DNS 레코드 생성 확인
- AWS Route53에서 A 레코드 자동 생성 확인
첫번째 실습 (ExternalDNS + DNS Provider : AWS Route53)
[AWS]
└─ Route53 Authoritative DNS Server
[WSL Ubuntu]
└─ kind cluster
├─ ExternalDNS (provider: aws)
├─ ingress-nginx controller
└─ test ingress & service
1. AWS에서 퍼블릭 도메인 구매

2. AWS Route53 호스팅 Zone 자동 생성 확인

3. AWS IAM 생성
- 생성 시 "AWS 외부에서 실행되는 애플리케이션" 에 체크
- 만들어진 Access Key & Secret Key 따로 저장

4. Host 서버에 credential 파일 생성
# mkdir /etc/aws/credentials
# cd /etc/aws/credentials
# cat <<EOF > credentials
[default]
aws_access_key_id=AKIAxxxxxxxxxxxxxxx
aws_secret_access_key=xxxxxxxxxxxxxxxxx
EOF
5. 클러스터에 ExternalDNS 설치
# helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
# helm repo update
# helm install externaldns external-dns/external-dns -n kube-system -f values.yaml
NAME: externaldns
LAST DEPLOYED: Tue Dec 9 12:05:16 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
* External DNS *
***********************************************************************
Chart version: 1.19.0
App version: 0.19.0
Image tag: registry.k8s.io/external-dns/external-dns:v0.19.0
***********************************************************************
provider:
name: aws
zoneType: public
env:
- name: AWS_SHARED_CREDENTIALS_FILE
value: /etc/aws/credentials/credentials
- name: AWS_REGION
value: ap-northeast-2
extraVolumes:
- name: aws-creds
secret:
secretName: external-dns-aws
extraVolumeMounts:
- name: aws-creds
mountPath: /etc/aws/credentials
readOnly: true
sources:
- service
- ingress
domainFilters:
- lake-devops.click
policy: sync
registry: txt
txtOwnerId: "externaldns-test"
logLevel: debug
(⎈|kind-myk8s:kube-system) root@DESKTOP-HUU6SC7:~/ria/externaldns# k logs -f externaldns-external-dns-58689db4d-rflxq
time="2025-12-09T03:05:17Z" level=info msg="config: {APIServerURL: KubeConfig: RequestTimeout:30s DefaultTargets:[] GlooNamespaces:[gloo-system] SkipperRouteGroupVersion:zalando.org/v1 Sources:[service ingress] Namespace: AnnotationFilter: LabelFilter: IngressClassNames:[] FQDNTemplate: CombineFQDNAndAnnotation:false IgnoreHostnameAnnotation:false IgnoreNonHostNetworkPods:false IgnoreIngressTLSSpec:false IgnoreIngressRulesSpec:false ListenEndpointEvents:false ExposeInternalIPV6:false GatewayName: GatewayNamespace: GatewayLabelFilter: Compatibility: PodSourceDomain: PublishInternal:false PublishHostIP:false AlwaysPublishNotReadyAddresses:false ConnectorSourceServer:localhost:8080 Provider:aws ProviderCacheTime:0s GoogleProject: GoogleBatchChangeSize:1000 GoogleBatchChangeInterval:1s GoogleZoneVisibility: DomainFilter:[lake-devops.click] ExcludeDomains:[] RegexDomainFilter: RegexDomainExclusion: ZoneNameFilter:[] ZoneIDFilter:[] TargetNetFilter:[] ExcludeTargetNets:[] AlibabaCloudConfigFile:/etc/kubernetes/alibaba-cloud.json AlibabaCloudZoneType: AWSZoneType: AWSZoneTagFilter:[] AWSAssumeRole: AWSProfiles:[] AWSAssumeRoleExternalID: AWSBatchChangeSize:1000 AWSBatchChangeSizeBytes:32000 AWSBatchChangeSizeValues:1000 AWSBatchChangeInterval:1s AWSEvaluateTargetHealth:true AWSAPIRetries:3 AWSPreferCNAME:false AWSZoneCacheDuration:0s AWSSDServiceCleanup:false AWSSDCreateTag:map[] AWSZoneMatchParent:false AWSDynamoDBRegion: AWSDynamoDBTable:external-dns AzureConfigFile:/etc/kubernetes/azure.json AzureResourceGroup: AzureSubscriptionID: AzureUserAssignedIdentityClientID: AzureActiveDirectoryAuthorityHost: AzureZonesCacheDuration:0s AzureMaxRetriesCount:3 CloudflareProxied:false CloudflareCustomHostnames:false CloudflareDNSRecordsPerPage:100 CloudflareDNSRecordsComment: CloudflareCustomHostnamesMinTLSVersion:1.0
CloudflareCustomHostnamesCertificateAuthority:none CloudflareRegionalServices:false CloudflareRegionKey: CoreDNSPrefix:/skydns/ AkamaiServiceConsumerDomain: AkamaiClientToken: AkamaiClientSecret: AkamaiAccessToken: AkamaiEdgercPath: AkamaiEdgercSection: OCIConfigFile:/etc/kubernetes/oci.yaml OCICompartmentOCID: OCIAuthInstancePrincipal:false OCIZoneScope:GLOBAL OCIZoneCacheDuration:0s InMemoryZones:[] OVHEndpoint:ovh-eu OVHApiRateLimit:20 OVHEnableCNAMERelative:false PDNSServer:http://localhost:8081 PDNSServerID:localhost PDNSAPIKey: PDNSSkipTLSVerify:false TLSCA: TLSClientCert: TLSClientCertKey: Policy:sync Registry:txt TXTOwnerID:externaldns-test TXTPrefix: TXTSuffix: TXTEncryptEnabled:false TXTEncryptAESKey: Interval:1m0s MinEventSyncInterval:5s Once:false DryRun:false UpdateEvents:false LogFormat:text MetricsAddress::7979 LogLevel:debug TXTCacheInterval:0s TXTWildcardReplacement: ExoscaleEndpoint: ExoscaleAPIKey: ExoscaleAPISecret: ExoscaleAPIEnvironment:api ExoscaleAPIZone:ch-gva-2 CRDSourceAPIVersion:externaldns.k8s.io/v1alpha1 CRDSourceKind:DNSEndpoint ServiceTypeFilter:[] CFAPIEndpoint: CFUsername: CFPassword: ResolveServiceLoadBalancerHostname:false RFC2136Host:[] RFC2136Port:0 RFC2136Zone:[] RFC2136Insecure:false RFC2136GSSTSIG:false RFC2136CreatePTR:false RFC2136KerberosRealm: RFC2136KerberosUsername: RFC2136KerberosPassword: RFC2136TSIGKeyName: RFC2136TSIGSecret: RFC2136TSIGSecretAlg: RFC2136TAXFR:false RFC2136MinTTL:0s RFC2136LoadBalancingStrategy:disabled RFC2136BatchChangeSize:50 RFC2136UseTLS:false RFC2136SkipTLSVerify:false NS1Endpoint: NS1IgnoreSSL:false NS1MinTTLSeconds:0 TransIPAccountName: TransIPPrivateKeyFile: DigitalOceanAPIPageSize:50 ManagedDNSRecordTypes:[A AAAA CNAME] ExcludeDNSRecordTypes:[] GoDaddyAPIKey: GoDaddySecretKey: GoDaddyTTL:0 GoDaddyOTE:false OCPRouterName: PiholeServer: PiholePassword: PiholeTLSInsecureSkipVerify:false PiholeApiVersion:5 PluralCluster: PluralProvider: WebhookProviderURL:http://localhost:8888 WebhookProviderReadTimeout:5s WebhookProviderWriteTimeout:10s WebhookServer:false TraefikEnableLegacy:false TraefikDisableNew:false NAT64Networks:[] ExcludeUnschedulable:true EmitEvents:[] ForceDefaultTargets:false sourceWrappers:map[]}"
time="2025-12-09T03:05:17Z" level=info msg="GitCommitShort=unknown, GoVersion=go1.24.6, Platform=linux/amd64, UserAgent=ExternalDNS/v20250902-v0.19.0"
time="2025-12-09T03:05:17Z" level=info msg="Instantiating new Kubernetes client"
time="2025-12-09T03:05:17Z" level=debug msg="apiServerURL: "
time="2025-12-09T03:05:17Z" level=debug msg="kubeConfig: "
time="2025-12-09T03:05:17Z" level=debug msg="serving 'healthz' on ':7979/healthz'"
time="2025-12-09T03:05:17Z" level=debug msg="serving 'metrics' on ':7979/metrics'"
time="2025-12-09T03:05:17Z" level=debug msg="registered '21' metrics"
time="2025-12-09T03:05:17Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2025-12-09T03:05:17Z" level=info msg="Created Kubernetes client https://10.96.0.1:443"
time="2025-12-09T03:05:17Z" level=debug msg="Refreshing zones list cache"
time="2025-12-09T03:05:18Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T03:05:19Z" level=debug msg="nat64Source: collecting endpoints and processing NAT64 translation"
time="2025-12-09T03:05:19Z" level=debug msg="dedupSource: collecting endpoints and removing duplicates"
time="2025-12-09T03:05:19Z" level=debug msg="multiSource: collecting endpoints from 2 child sources and removing duplicates"
time="2025-12-09T03:05:19Z" level=debug msg="No endpoints could be generated from service default/kubernetes"
time="2025-12-09T03:05:19Z" level=debug msg="No endpoints could be generated from service ingress-nginx/ingress-nginx-controller"
time="2025-12-09T03:05:19Z" level=debug msg="No endpoints could be generated from service ingress-nginx/ingress-nginx-controller-admission"
time="2025-12-09T03:05:19Z" level=debug msg="No endpoints could be generated from service kube-system/externaldns-external-dns"
time="2025-12-09T03:05:19Z" level=debug msg="No endpoints could be generated from service kube-system/kube-dns"
time="2025-12-09T03:05:19Z" level=debug msg="Refreshing zones list cache"
time="2025-12-09T03:05:19Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T03:05:19Z" level=info msg="Applying provider record filter for domains: [lake-devops.click. .lake-devops.click.]"
time="2025-12-09T03:05:19Z" level=info msg="All records are already up to date"
6. Nginx Ingress Controller 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
7. 테스트용 Ingress 배포
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-externaldns
spec:
rules:
- host: user.lake-devops.click
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
8. DNS 자동 등록 확인
time="2025-12-09T03:19:52Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T03:19:52Z" level=info msg="Applying provider record filter for domains: [lake-devops.click. .lake-devops.click.]"
time="2025-12-09T03:19:52Z" level=debug msg="Refreshing zones list cache"
time="2025-12-09T03:19:52Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T03:19:52Z" level=debug msg="Adding user.lake-devops.click. to zone lake-devops.click. [Id: /hostedzone/Z03737533SGX7MJ3SQBQV]"
time="2025-12-09T03:19:52Z" level=debug msg="Adding cname-user.lake-devops.click. to zone lake-devops.click. [Id: /hostedzone/Z03737533SGX7MJ3SQBQV]"
time="2025-12-09T03:19:52Z" level=info msg="Desired change: CREATE cname-user.lake-devops.click TXT" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.
time="2025-12-09T03:19:52Z" level=info msg="Desired change: CREATE user.lake-devops.click CNAME" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.
time="2025-12-09T03:19:52Z" level=info msg="2 record(s) were successfully updated" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.

9. ingress 삭제 후 도메인 업데이트 확인
time="2025-12-09T03:26:34Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T03:26:34Z" level=info msg="Applying provider record filter for domains: [lake-devops.click. .lake-devops.click.]"
time="2025-12-09T03:26:34Z" level=debug msg="Refreshing zones list cache"
time="2025-12-09T03:26:34Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T03:26:34Z" level=debug msg="Adding user.lake-devops.click. to zone lake-devops.click. [Id: /hostedzone/Z03737533SGX7MJ3SQBQV]"
time="2025-12-09T03:26:34Z" level=debug msg="Adding cname-user.lake-devops.click. to zone lake-devops.click. [Id: /hostedzone/Z03737533SGX7MJ3SQBQV]"
time="2025-12-09T03:26:34Z" level=info msg="Desired change: DELETE cname-user.lake-devops.click TXT" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.
time="2025-12-09T03:26:34Z" level=info msg="Desired change: DELETE user.lake-devops.click CNAME" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.
time="2025-12-09T03:26:34Z" level=info msg="2 record(s) were successfully updated" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.

추가 테스트 (Service)
1. LoadBalancer External-IP 생성을 위한 MetalLB 설치
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml
2. IP 풀 생성
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: my-ip-pool
namespace: metallb-system
spec:
addresses:
- 172.18.255.1-172.18.255.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: my-l2adv
namespace: metallb-system
spec:
ipAddressPools:
- my-ip-pool
EOF
3. 테트리스 샘플 pod, service 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris
spec:
replicas: 1
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
name: tetris
annotations:
external-dns.alpha.kubernetes.io/hostname: "tetris.lake-devops.click"
spec:
type: LoadBalancer
selector:
app: tetris
ports:
- port: 80
targetPort: 80
EOF
4. ExternalDNS 로그 확인
time="2025-12-09T05:09:47Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T05:09:47Z" level=info msg="Applying provider record filter for domains: [lake-devops.click. .lake-devops.click.]"
time="2025-12-09T05:09:47Z" level=debug msg="Refreshing zones list cache"
time="2025-12-09T05:09:47Z" level=debug msg="Considering zone: /hostedzone/Z03737533SGX7MJ3SQBQV (domain: lake-devops.click.)"
time="2025-12-09T05:09:47Z" level=debug msg="Adding tetris.lake-devops.click. to zone lake-devops.click. [Id: /hostedzone/Z03737533SGX7MJ3SQBQV]"
time="2025-12-09T05:09:47Z" level=debug msg="Adding a-tetris.lake-devops.click. to zone lake-devops.click. [Id: /hostedzone/Z03737533SGX7MJ3SQBQV]"
time="2025-12-09T05:09:47Z" level=info msg="Desired change: CREATE a-tetris.lake-devops.click TXT" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.
time="2025-12-09T05:09:47Z" level=info msg="Desired change: CREATE tetris.lake-devops.click A" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.
time="2025-12-09T05:09:47Z" level=info msg="2 record(s) were successfully updated" profile=default zoneID=/hostedzone/Z03737533SGX7MJ3SQBQV zoneName=lake-devops.click.

# kubectl get svc tetris
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tetris LoadBalancer 10.96.27.131 172.18.255.2 80:30370/TCP 2m5s
두번째 실습 (ExternalDNS + DNS Provider : Linux Bind9)
[WSL Ubuntu]
├─ bind9 DNS Server
└─ kind cluster
├─ ExternalDNS (provider: rfc2136)
├─ ingress-nginx controller
└─ test service
1. bind9 설치
sudo apt install bind9 bind9utils bind9-doc -y
# cat /etc/bind/named.conf.options
options {
directory "/var/cache/bind";
// If there is a firewall between you and nameservers you want
// to talk to, you may need to fix the firewall to allow multiple
// ports to talk. See http://www.kb.cert.org/vuls/id/800113
// If your ISP provided one or more IP addresses for stable
// nameservers, you probably want to use them as forwarders.
// Uncomment the following block, and insert the addresses replacing
// the all-0's placeholder.
// forwarders {
// 0.0.0.0;
// };
//========================================================================
// If BIND logs error messages about the root key being expired,
// you will need to update your keys. See https://www.isc.org/bind-keys
$TTL 86400
//========================================================================
dnssec-validation auto;
listen-on-v6 { any; };
};
2. bind9 기본 설정 변경 (/etc/bind/named.conf.local 파일 수정)
tsig-keygen -a hmac-sha256 externaldns
# cat /etc/bind/named.conf.local
//
// Do any local configuration here
//
// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";
key "externaldns" {
algorithm hmac-sha256;
secret "88OM9SE9wUp91peYxNUH+JbAhsXl1n4iCw+OFmYjY3o=";
};
zone "lake-devops.com" IN {
type master;
file "lake-devops.com.zone";
allow-transfer {
key "externaldns";
};
update-policy {
grant externaldns zonesub ANY;
};
};
3. Zone 파일 작성 (/var/cache/bind/lake-devops.com.zone 파일 수정)
$TTL 86400
@ IN SOA ns1.lake-devops.com. admin.lake-devops.com. (
1
3600
900
604800
86400
)
@ IN NS ns1.lake-devops.com.
ns1 IN A 172.24.148.155
4. bind9 설정 검사
# named-checkzone lake-devops.com /var/cache/bind/lake-devops.com.zone
zone lake-devops.com/IN: loaded serial 1
OK
5. bind9 재시작
#sudo systemctl restart bind9
6. /etc/resolv.conf 설정 변경
# cat /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 172.24.144.1
nameserver 127.0.0.1
7. ExternalDNS 설정 변경
helm uninstall externaldns -n kube-system
# cat values_bind9.yaml
provider:
name: rfc2136
extraArgs:
- --rfc2136-host=172.24.148.155
- --rfc2136-port=53
- --rfc2136-zone=lake-devops.com
- --rfc2136-tsig-keyname=externaldns
- --rfc2136-tsig-secret=88OM9SE9wUp91peYxNUH+JbAhsXl1n4iCw+OFmYjY3o=
- --rfc2136-tsig-secret-alg=hmac-sha256
sources:
- service
- ingress
domainFilters:
- lake-devops.com
policy: sync
registry: txt
txtOwnerId: "externaldns-test"
logLevel: debug
# helm install externaldns external-dns/external-dns -n kube-system -f values_bind9.yaml
# k logs -f externaldns-external-dns-766ff7958b-xkr5d
time="2025-12-09T07:25:36Z" level=info msg="config: {APIServerURL: KubeConfig: RequestTimeout:30s DefaultTargets:[] GlooNamespaces:[gloo-system] SkipperRouteGroupVersion:zalando.org/v1 Sources:[service ingress] Namespace: AnnotationFilter: LabelFilter: IngressClassNames:[] FQDNTemplate: CombineFQDNAndAnnotation:false IgnoreHostnameAnnotation:false IgnoreNonHostNetworkPods:false IgnoreIngressTLSSpec:false IgnoreIngressRulesSpec:false ListenEndpointEvents:false ExposeInternalIPV6:false GatewayName: GatewayNamespace: GatewayLabelFilter: Compatibility: PodSourceDomain: PublishInternal:false PublishHostIP:false AlwaysPublishNotReadyAddresses:false ConnectorSourceServer:localhost:8080 Provider:rfc2136 ProviderCacheTime:0s GoogleProject: GoogleBatchChangeSize:1000 GoogleBatchChangeInterval:1s GoogleZoneVisibility: DomainFilter:[lake-devops.com] ExcludeDomains:[] RegexDomainFilter: RegexDomainExclusion: ZoneNameFilter:[] ZoneIDFilter:[] TargetNetFilter:[] ExcludeTargetNets:[] AlibabaCloudConfigFile:/etc/kubernetes/alibaba-cloud.json AlibabaCloudZoneType: AWSZoneType: AWSZoneTagFilter:[] AWSAssumeRole: AWSProfiles:[] AWSAssumeRoleExternalID: AWSBatchChangeSize:1000 AWSBatchChangeSizeBytes:32000 AWSBatchChangeSizeValues:1000 AWSBatchChangeInterval:1s AWSEvaluateTargetHealth:true AWSAPIRetries:3 AWSPreferCNAME:false AWSZoneCacheDuration:0s AWSSDServiceCleanup:false
AWSSDCreateTag:map[] AWSZoneMatchParent:false AWSDynamoDBRegion: AWSDynamoDBTable:external-dns AzureConfigFile:/etc/kubernetes/azure.json AzureResourceGroup: AzureSubscriptionID: AzureUserAssignedIdentityClientID: AzureActiveDirectoryAuthorityHost: AzureZonesCacheDuration:0s AzureMaxRetriesCount:3 CloudflareProxied:false CloudflareCustomHostnames:false CloudflareDNSRecordsPerPage:100 CloudflareDNSRecordsComment: CloudflareCustomHostnamesMinTLSVersion:1.0
CloudflareCustomHostnamesCertificateAuthority:none CloudflareRegionalServices:false CloudflareRegionKey: CoreDNSPrefix:/skydns/ AkamaiServiceConsumerDomain: AkamaiClientToken: AkamaiClientSecret: AkamaiAccessToken: AkamaiEdgercPath: AkamaiEdgercSection: OCIConfigFile:/etc/kubernetes/oci.yaml OCICompartmentOCID: OCIAuthInstancePrincipal:false OCIZoneScope:GLOBAL OCIZoneCacheDuration:0s InMemoryZones:[] OVHEndpoint:ovh-eu OVHApiRateLimit:20 OVHEnableCNAMERelative:false PDNSServer:http://localhost:8081 PDNSServerID:localhost PDNSAPIKey: PDNSSkipTLSVerify:false TLSCA: TLSClientCert: TLSClientCertKey: Policy:sync Registry:txt TXTOwnerID:externaldns-test TXTPrefix: TXTSuffix: TXTEncryptEnabled:false TXTEncryptAESKey: Interval:1m0s MinEventSyncInterval:5s Once:false DryRun:false UpdateEvents:false LogFormat:text MetricsAddress::7979 LogLevel:debug TXTCacheInterval:0s TXTWildcardReplacement: ExoscaleEndpoint: ExoscaleAPIKey: ExoscaleAPISecret: ExoscaleAPIEnvironment:api ExoscaleAPIZone:ch-gva-2 CRDSourceAPIVersion:externaldns.k8s.io/v1alpha1 CRDSourceKind:DNSEndpoint ServiceTypeFilter:[] CFAPIEndpoint: CFUsername: CFPassword: ResolveServiceLoadBalancerHostname:false RFC2136Host:[172.24.148.155] RFC2136Port:53 RFC2136Zone:[lake-devops.com] RFC2136Insecure:false RFC2136GSSTSIG:false RFC2136CreatePTR:false RFC2136KerberosRealm: RFC2136KerberosUsername: RFC2136KerberosPassword: RFC2136TSIGKeyName:externaldns RFC2136TSIGSecret:****** RFC2136TSIGSecretAlg:hmac-sha256 RFC2136TAXFR:false RFC2136MinTTL:0s RFC2136LoadBalancingStrategy:disabled RFC2136BatchChangeSize:50 RFC2136UseTLS:false RFC2136SkipTLSVerify:false NS1Endpoint: NS1IgnoreSSL:false NS1MinTTLSeconds:0 TransIPAccountName: TransIPPrivateKeyFile: DigitalOceanAPIPageSize:50 ManagedDNSRecordTypes:[A AAAA CNAME] ExcludeDNSRecordTypes:[] GoDaddyAPIKey: GoDaddySecretKey: GoDaddyTTL:0 GoDaddyOTE:false OCPRouterName: PiholeServer: PiholePassword: PiholeTLSInsecureSkipVerify:false PiholeApiVersion:5 PluralCluster: PluralProvider: WebhookProviderURL:http://localhost:8888 WebhookProviderReadTimeout:5s WebhookProviderWriteTimeout:10s WebhookServer:false TraefikEnableLegacy:false TraefikDisableNew:false NAT64Networks:[] ExcludeUnschedulable:true EmitEvents:[] ForceDefaultTargets:false sourceWrappers:map[]}"
time="2025-12-09T07:25:36Z" level=info msg="GitCommitShort=unknown, GoVersion=go1.24.6, Platform=linux/amd64, UserAgent=ExternalDNS/v20250902-v0.19.0"
time="2025-12-09T07:25:36Z" level=info msg="Instantiating new Kubernetes client"
time="2025-12-09T07:25:36Z" level=debug msg="serving 'healthz' on ':7979/healthz'"
time="2025-12-09T07:25:36Z" level=debug msg="serving 'metrics' on ':7979/metrics'"
time="2025-12-09T07:25:36Z" level=debug msg="registered '21' metrics"
time="2025-12-09T07:25:36Z" level=debug msg="apiServerURL: "
time="2025-12-09T07:25:36Z" level=debug msg="kubeConfig: "
time="2025-12-09T07:25:36Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2025-12-09T07:25:36Z" level=info msg="Created Kubernetes client https://10.96.0.1:443"
time="2025-12-09T07:25:36Z" level=info msg="Configured RFC2136 with zones '[lake-devops.com]' and nameservers '[172.24.148.155]'"
time="2025-12-09T07:25:36Z" level=debug msg="axfr is disabled"
time="2025-12-09T07:25:36Z" level=debug msg="nat64Source: collecting endpoints and processing NAT64 translation"
time="2025-12-09T07:25:36Z" level=debug msg="dedupSource: collecting endpoints and removing duplicates"
time="2025-12-09T07:25:36Z" level=debug msg="multiSource: collecting endpoints from 2 child sources and removing duplicates"
time="2025-12-09T07:25:36Z" level=debug msg="No endpoints could be generated from service metallb-system/webhook-service"
time="2025-12-09T07:25:36Z" level=debug msg="No endpoints could be generated from service default/kubernetes"
time="2025-12-09T07:25:36Z" level=debug msg="No endpoints could be generated from service ingress-nginx/ingress-nginx-controller"
time="2025-12-09T07:25:36Z" level=debug msg="No endpoints could be generated from service ingress-nginx/ingress-nginx-controller-admission"
time="2025-12-09T07:25:36Z" level=debug msg="No endpoints could be generated from service kube-system/externaldns-external-dns"
time="2025-12-09T07:25:36Z" level=debug msg="No endpoints could be generated from service kube-system/kube-dns"
time="2025-12-09T07:25:36Z" level=info msg="All records are already up to date"
8. 테스트 서비스 배포
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
external-dns.alpha.kubernetes.io/hostname: app.lake-devops.com
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
9. ExternalDNS 로그 확인
time="2025-12-09T07:31:05Z" level=debug msg="AddRecord.ep=app.lake-devops.com 0 IN A 172.18.255.29 []"
time="2025-12-09T07:31:05Z" level=info msg="Adding RR: app.lake-devops.com 0 A 172.18.255.29"
time="2025-12-09T07:31:05Z" level=debug msg="AddRecord.ep=a-app.lake-devops.com 0 IN TXT \"heritage=external-dns,external-dns/owner=externaldns-test,external-dns/resource=service/kube-system/my-service\" []"
time="2025-12-09T07:31:05Z" level=info msg="Adding RR: a-app.lake-devops.com 0 TXT \"heritage=external-dns,external-dns/owner=externaldns-test,external-dns/resource=service/kube-system/my-service\""
time="2025-12-09T07:31:05Z" level=debug msg=SendMessage
time="2025-12-09T07:31:05Z" level=debug msg="Sending message to nameserver: 172.24.148.155:53"
time="2025-12-09T07:31:05Z" level=debug msg=SendMessage.success
(⎈|kind-exdns-k8s:N/A) root@DESKTOP-HUU6SC7:/etc/bind# cat /var/cache/bind/lake-devops.com.zone
$ORIGIN .
$TTL 86400 ; 1 day
lake-devops.com IN SOA ns1.lake-devops.com. admin.lake-devops.com. (
6 ; serial
3600 ; refresh (1 hour)
900 ; retry (15 minutes)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
NS ns1.lake-devops.com.
$ORIGIN lake-devops.com.
$TTL 0 ; 0 seconds
a-app TXT "heritage=external-dns,external-dns/owner=externaldns-test,external-dns/resource=service/kube-system/my-service"
a-tetris TXT "heritage=external-dns,external-dns/owner=externaldns-test,external-dns/resource=service/default/tetris"
app A 172.18.255.29
cname-tetris TXT "heritage=external-dns,external-dns/owner=externaldns-test,external-dns/resource=ingress/default/tetris-ingress"
$TTL 86400 ; 1 day
ns1 A 172.24.148.155
$TTL 0 ; 0 seconds
tetris CNAME localhost.
추가 테스트 (웹 접속 확인)
[WSL Ubuntu]
├─ bind9 DNS Server
├─ kind cluster
│ ├─ ExternalDNS (provider: rfc2136)
│ ├─ ingress-nginx controller
│ │ (listens on containerPort 80/443)
│ └─ tetris service,ingress
└─ extraPortMappings
(hostPort 80 → kind control-plane :80)
(hostPort 443 → kind control-plane :443)
curl test (WSL/Windows) - ExternalDNS랑 별개로 접속 테스트용
curl http://tetris.example.com
↓
DNS → 127.0.0.1
↓
hostPort 80
↓
kind ingress controller
↓
tetris service (ClusterIP)
↓
Pod
1. Cluster 설치
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
- role: worker
2. ingress nginx, metallb 설치 (ip-pool 생성)
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: pool1
namespace: metallb-system
spec:
addresses:
- 172.18.255.100-172.18.255.110
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2advertisement1
namespace: metallb-system
spec:
ipAddressPools:
- pool1
3. externaldns 설치
provider:
name: rfc2136
extraArgs:
- --rfc2136-host=172.24.148.155
- --rfc2136-port=53
- --rfc2136-zone=lake-devops.com
- --rfc2136-tsig-keyname=externaldns
- --rfc2136-tsig-secret=88OM9SE9wUp91peYxNUH+JbAhsXl1n4iCw+OFmYjY3o=
- --rfc2136-tsig-secret-alg=hmac-sha256
sources:
- service
- ingress
domainFilters:
- lake-devops.com
policy: sync
registry: txt
txtOwnerId: "externaldns-test"
logLevel: debug
4. pod, service, ingress 배포 후 확인
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris
spec:
replicas: 1
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
name: tetris
spec:
type: ClusterIP
selector:
app: tetris
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tetris-ingress
annotations:
external-dns.alpha.kubernetes.io/hostname: tetris.lake-devops.com
spec:
ingressClassName: nginx
rules:
- host: tetris.lake-devops.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tetris
port:
number: 80
time="2025-12-09T08:34:26Z" level=debug msg="axfr is disabled"
time="2025-12-09T08:34:26Z" level=debug msg="nat64Source: collecting endpoints and processing NAT64 translation"
time="2025-12-09T08:34:26Z" level=debug msg="dedupSource: collecting endpoints and removing duplicates"
time="2025-12-09T08:34:26Z" level=debug msg="multiSource: collecting endpoints from 2 child sources and removing duplicates"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service ingress-nginx/ingress-nginx-controller"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service ingress-nginx/ingress-nginx-controller-admission"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service kube-system/externaldns-external-dns"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service kube-system/kube-dns"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service metallb-system/webhook-service"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service default/tetris"
time="2025-12-09T08:34:26Z" level=debug msg="No endpoints could be generated from service default/kubernetes"
time="2025-12-09T08:34:26Z" level=debug msg="Endpoints generated from ingress: default/tetris-ingress: [tetris.lake-devops.com 0 IN CNAME localho IN CNAME localhost []]"
time="2025-12-09T08:34:26Z" level=debug msg="Removing duplicate endpoint tetris.lake-devops.com 0 IN CNAME localhost []"
time="2025-12-09T08:34:26Z" level=debug msg="ApplyChanges (Create: 2, UpdateOld: 0, UpdateNew: 0, Delete: 0)"
time="2025-12-09T08:34:26Z" level=debug msg="Processing batch 0 of create changes"
time="2025-12-09T08:34:26Z" level=debug msg="AddRecord.ep=tetris.lake-devops.com 0 IN CNAME localhost []"
time="2025-12-09T08:34:26Z" level=info msg="Adding RR: tetris.lake-devops.com 0 CNAME localhost"
time="2025-12-09T08:34:26Z" level=debug msg="AddRecord.ep=cname-tetris.lake-devops.com 0 IN TXT \"heritage=external-dns,external-dns/owner=externce=ingress/default/tetris-ingress\" []"
time="2025-12-09T08:34:26Z" level=info msg="Adding RR: cname-tetris.lake-devops.com 0 TXT \"heritage=external-dns,external-dns/owner=externaldns-tess/default/tetris-ingress\""
time="2025-12-09T08:34:26Z" level=debug msg=SendMessage
time="2025-12-09T08:34:26Z" level=debug msg="Sending message to nameserver: 172.24.148.155:53"
time="2025-12-09T08:34:26Z" level=debug msg=SendMessage.success
5. 도메인 질의 확인
# dig @172.24.148.155 tetris.lake-devops.com
; <<>> DiG 9.18.39-0ubuntu0.24.04.2-Ubuntu <<>> @172.24.148.155 tetris.lake-devops.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13523
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: b7ed6e0cd541cca7010000006937ed9bc2b70f2bfe00e928 (good)
;; QUESTION SECTION:
;tetris.lake-devops.com. IN A
;; ANSWER SECTION:
tetris.lake-devops.com. 0 IN CNAME localhost.
localhost. 604800 IN A 127.0.0.1
;; Query time: 0 msec
;; SERVER: 172.24.148.155#53(172.24.148.155) (UDP)
;; WHEN: Tue Dec 09 18:36:27 KST 2025
;; MSG SIZE rcvd: 118
[번외]
> tetris 서비스 접속 확인
# curl -v http://tetris.lake-devops.com
* Host tetris.lake-devops.com:80 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:80...
* connect to ::1 port 80 from ::1 port 57266 failed: Connection refused
* Trying 127.0.0.1:80...
* Connected to tetris.lake-devops.com (127.0.0.1) port 80
> GET / HTTP/1.1
> Host: tetris.lake-devops.com
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 09 Dec 2025 09:36:06 GMT
< Content-Type: text/html
< Content-Length: 832
< Connection: keep-alive
< Last-Modified: Sun, 20 Mar 2022 03:31:50 GMT
< ETag: "6236a026-340"
< Accept-Ranges: bytes
<
<html>
<head>
<title>Tetris</title>
<link href="https://fonts.googleapis.com/css?family=Russo+One&display=swap" rel="stylesheet">
<style>
.parent {
display: flex;
justify-content: center;
align-items: center;
}
#score {
font-size:2em
}
#tetris {
height: 90vh;
max-height: 900px
}
</style>
</head>
<body class="parent" style="background: #202025; font-family: 'Russo One', sans-serif;" >
<div style="color:white; background: #111; padding: 1em; border-radius: 5px">
<canvas id="tetris" width="480" height="900" style="border-radius: 5px"></canvas>
<script src="tetris.js"></script>
</div>
</body>
* Connection #0 to host tetris.lake-devops.com left intact
> window hosts 등록
127.0.0.1 tetris.lake-devops.com

추가 개념 정리
TSIG (보안)
- Transaction SIGnature
- DNS 패킷을 인증하기 위한 공유키 기반 HMAC 인증 방식
- DNS 프로토콜(RFC2136, AXFR)를 이용해 zone을 관리하는 authoritative DNS 서버에서 사용됨
- RFC2136 동적 업데이트 할때 또는 Zone Transfer (AXFR/IXFR) 보안을 위해 사용
- key name + secret 기반
- 인증, 무결성, 재전송 보호. DNS는 Plaintext라서 취약성 보완을 위해 사용
AXFR (데이터 복제)
- Authoritative Zone TransFeR
- DNS 영역 전체를 복제하는 프로토콜
- BIND 계열 authoritative DNS 서버에서 사용
- Primary DNS 서버에 있는 zone 파일 내용을 Secondary DNS 서버로 전부 복제하는 과정
- 고가용성/백업을 위해 필수
- Secondary가 Primary보다 빠르게 응답하도록 Local DNS를 구성할 때에도 사용
ExternalDNS 와 관계 (Delete 작업 - AXFR)
- TSIG 인증은 선택적으로 사용가능
- 의문점은 RFC2136 동적 업데이트 할 때 특히 DELETE 작업 시
AXFR이 없어도 UPDATE하는 allow-update 권한이랑, TXT 조회하는 allow-query만 있으면 되지 않나 - 하지만 실제 DELETE 작업시, AXFR을 활성화해야만 DELETE 작업이 수행됨
[해결]
external-dns/docs/tutorials/rfc2136.md at master · kubernetes-sigs/external-dns · GitHub

- ExternalDNS는 RFC2136 UPDATE를 통해 레코드를 삭제하지만,
RFC2136 provider의 구현상 삭제 대상 레코드를 식별하기 위해 zone transfer(AXFR) 사용.
공식 문서에서도 DELETE 작업을 수행하려면 AXFR 활성화하라고 명시되어 있음 - DELETE 동작 흐름
1. external-dns가 AXFR을 통해 zone 전체 레코드를 읽음
2. TXT registry 기반으로 내가 관리하는 레코드 식별
3. Kubernetes desired state 와 비교
4. 삭제 대상 결정
5. RFC2136 UPDATE(delete) 실행 -> 이땐 AXFR 필요 없음
출처
external-dns/docs/tutorials/aws.md at master · kubernetes-sigs/external-dns · GitHub
external-dns/docs/tutorials/aws.md at master · kubernetes-sigs/external-dns
Configure external DNS servers dynamically from Kubernetes resources - kubernetes-sigs/external-dns
github.com
[AWS] 📚 Route53 개념 원리 & 사용 세팅 💯 정리
[AWS] 📚 Route53 개념 원리 & 사용 세팅 💯 정리
Route 53 Amazon Route 53 은 가용성과 확장성이 뛰어난 클라우드 Domain Name System (DNS) 웹 서비스이다. Route 53는 도메인 구입부터 네임서버 등록까지 dns에 필요한 모든 기능이 있고, aws 답게 추가로 모니
inpa.tistory.com
[Kubernetes] ingress - external dns - route53(cloud dns server)
[Kubernetes] ingress - external dns - route53(cloud dns server)
여기서 중요한 것은 ExternalDNS임 만약 public 이 필요 없다면 CoreDNS + Traefik 로 사용 정의external dns란 쿠버네티스 클러스터 내부 리소스를 외부 DNS 서비스와 연동하여 DNS 레코드를 자동으로 생성하
luv-n-interest.tistory.com