external-dns-tencentcloud-webhook
May 12, 2026 · View on GitHub
ExternalDNS webhook provider for Tencent Cloud DNS services (DNSPod & PrivateDNS).
Overview
ExternalDNS upstream removed the in-tree tencentcloud provider and recommends the webhook approach. This project implements the webhook protocol as a standalone sidecar.
| Service | Zone Type | Description |
|---|---|---|
| DNSPod | public | Public DNS resolution |
| PrivateDNS | private | VPC-scoped internal DNS |
Architecture
┌──────────────────────────────── Kubernetes Cluster ──────────────────────────────────┐
│ │
│ ┌── external-dns-public ──────────┐ ┌── external-dns-private ──────────────┐ │
│ │ │ │ │ │
│ │ external-dns ──▶ webhook │ │ external-dns ──▶ webhook │ │
│ │ --zone=public │ │ --zone=private │ │
│ │ --domain= │ │ --domain= │ │
│ │ example.com │ │ internal.example │ │
│ │ │ │ --vpc-id=vpc-xxx │ │
│ └───────────────┬─────────────────┘ └───────────────┬──────────────────────┘ │
│ │ │ │
└──────────────────┼────────────────────────────────────────┼──────────────────────────┘
▼ ▼
DNSPod API PrivateDNS API
(public resolution) (VPC-scoped resolution)
Each zone type is a separate Helm release with independent RBAC and credentials. This follows the ExternalDNS recommended pattern — separate instances per zone type for security isolation (least-privilege CAM roles) and operational safety (prevent internal services from modifying public DNS).
Only need one zone type? Just deploy one release. The second is optional.
Installation
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
Credential mode A: OIDC (recommended)
No static keys needed. Requires:
- TKE managed cluster with version
>= v1.20.6-tke.27or>= v1.22.5-tke.1 - OIDC enabled (TKE Console → Cluster → Basic Info → ServiceAccountIssuerDiscovery)
One-command setup (creates CAM role + generates values):
# Public zone
curl -sSL https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/scripts/setup.sh \
| bash -s -- --auth oidc --zone public --domain example.com
# Private zone
curl -sSL https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/scripts/setup.sh \
| bash -s -- --auth oidc --zone private --domain internal.example.com
Then install:
helm install external-dns-public external-dns/external-dns \
-n external-dns --create-namespace \
-f https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/deploy/helm/values-base.yaml \
-f values-public.local.yaml
# China mainland: add values-china.yaml for CCR-hosted images
helm install external-dns-public external-dns/external-dns \
-n external-dns --create-namespace \
-f https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/deploy/helm/values-base.yaml \
-f https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/deploy/helm/values-china.yaml \
-f values-public.local.yaml
Manual values — public zone (without setup script)
Requires CAM role created beforehand. See Prerequisites — OIDC setup for detailed steps.
# values-public.yaml
serviceAccount:
annotations:
tke.cloud.tencent.com/role-arn: "qcs::cam::uin/<UIN>:roleName/external-dns-<CLUSTER_ID>-public"
tke.cloud.tencent.com/audience: "sts.cloud.tencent.com"
tke.cloud.tencent.com/token-expiration: "86400"
domainFilters:
- example.com
txtOwnerId: <CLUSTER_ID>-public
provider:
webhook:
args:
- --credential-mode=oidc
- --zone-type=public
- --domain-filter=example.com
Manual values — private zone (without setup script)
Requires CAM role with QcloudPrivateDNSFullAccess policy. See Prerequisites — OIDC setup.
# values-private.yaml
serviceAccount:
annotations:
tke.cloud.tencent.com/role-arn: "qcs::cam::uin/<UIN>:roleName/external-dns-<CLUSTER_ID>-private"
tke.cloud.tencent.com/audience: "sts.cloud.tencent.com"
tke.cloud.tencent.com/token-expiration: "86400"
domainFilters:
- internal.example.com
txtOwnerId: <CLUSTER_ID>-private
extraArgs:
- --publish-internal-services
provider:
webhook:
args:
- --credential-mode=oidc
- --zone-type=private
- --domain-filter=internal.example.com
- --vpc-id=vpc-xxxxxxxx
Uses
QcloudPrivateDNSFullAccesspolicy.--publish-internal-servicesenables ClusterIP Services to create DNS records.
Details: OIDC guide | Prerequisites
Credential mode B: Static credentials
Works on any Kubernetes cluster (TKE, self-managed, EKS, etc). No cluster type or version restriction.
Using the setup script (creates Secret + generates values):
curl -sSL https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/scripts/setup.sh \
| bash -s -- --auth static --zone public --domain example.com
Or manually:
# 1. Create Secret
kubectl create namespace external-dns
kubectl -n external-dns create secret generic tencentcloud-credentials \
--from-literal=TENCENTCLOUD_SECRET_ID=<ID> \
--from-literal=TENCENTCLOUD_SECRET_KEY=<KEY>
# 2. Install
helm install external-dns-public external-dns/external-dns \
-n external-dns \
-f https://raw.githubusercontent.com/tkestack/external-dns-tencentcloud-webhook/master/deploy/helm/values-base.yaml \
-f values-static.yaml
values-static.yaml — public zone
domainFilters:
- example.com
txtOwnerId: <CLUSTER_ID>-public
provider:
webhook:
args:
- --zone-type=public
- --domain-filter=example.com
env:
- name: TENCENTCLOUD_SECRET_ID
valueFrom:
secretKeyRef:
name: tencentcloud-credentials
key: TENCENTCLOUD_SECRET_ID
- name: TENCENTCLOUD_SECRET_KEY
valueFrom:
secretKeyRef:
name: tencentcloud-credentials
key: TENCENTCLOUD_SECRET_KEY
- name: TENCENTCLOUD_REGION
value: "ap-guangzhou"
values-static.yaml — private zone
domainFilters:
- internal.example.com
txtOwnerId: <CLUSTER_ID>-private
extraArgs:
- --publish-internal-services
provider:
webhook:
args:
- --zone-type=private
- --domain-filter=internal.example.com
- --vpc-id=vpc-xxxxxxxx
env:
- name: TENCENTCLOUD_SECRET_ID
valueFrom:
secretKeyRef:
name: tencentcloud-credentials
key: TENCENTCLOUD_SECRET_ID
- name: TENCENTCLOUD_SECRET_KEY
valueFrom:
secretKeyRef:
name: tencentcloud-credentials
key: TENCENTCLOUD_SECRET_KEY
- name: TENCENTCLOUD_REGION
value: "ap-guangzhou"
Details: Static credentials guide
Managing both public and private zones
Deploy two separate Helm releases (external-dns-public + external-dns-private), each with its own permissions and values. See public-private-zones guide.
Verify
kubectl -n external-dns get pods
# external-dns-public-xxx 2/2 Running
kubectl -n external-dns logs -l app.kubernetes.io/instance=external-dns-public -c webhook
# "Starting webhook provider server on localhost:8888"
Uninstall
helm uninstall external-dns-public -n external-dns
Configuration
| Webhook arg | Default | Description |
|---|---|---|
--credential-mode | static | static or oidc (TKE RRSA) |
--zone-type | public | public (DNSPod) or private (PrivateDNS) |
--domain-filter | Limit to specific domain (repeatable) | |
--vpc-id | VPC ID (required for private zone) | |
--dry-run | false | Log changes without applying |
Full reference: docs/reference/configuration.md
Guides
| Topic | Link |
|---|---|
| Prerequisites (OIDC vs Static) | docs/getting-started/prerequisites.md |
| OIDC mode: how it works + troubleshooting | docs/guides/credential-oidc.md |
| Static mode: config file migration | docs/guides/credential-static.md |
| Managing public + private zones | docs/guides/public-private-zones.md |
| Migrating from in-tree provider | docs/guides/migration.md |
| Provider-specific annotations | docs/reference/annotations.md |
| FAQ | external-dns official FAQ |
Development
See docs/development/ for building, releasing, and Taskfile reference.
License
Apache License 2.0 — see LICENSE.