|
| 1 | +:_content-type: ASSEMBLY |
| 2 | +[id="cloud-experts-external-dns"] |
| 3 | += Tutorial: Deploying the External DNS Operator on ROSA |
| 4 | +include::_attributes/attributes-openshift-dedicated.adoc[] |
| 5 | +:context: cloud-experts-external-dns |
| 6 | + |
| 7 | +toc::[] |
| 8 | + |
| 9 | +//Mobb content metadata |
| 10 | +//Brought into ROSA product docs 2023-09-20 |
| 11 | +//--- |
| 12 | +//date: '2021-06-10' |
| 13 | +//title: External DNS for ROSA custom domain |
| 14 | +//weight: 1 |
| 15 | +//tags: ["AWS", "ROSA"] |
| 16 | +//authors: |
| 17 | +// - Chris Kang |
| 18 | +// - Dustin Scott |
| 19 | +//--- |
| 20 | + |
| 21 | +Configuring the xref:../applications/deployments/osd-config-custom-domains-applications.adoc[Custom Domain Operator] requires a wildcard CNAME DNS record in your Amazon Route 53 hosted zone. If you do not want to use a wildcard record, you can use the `External DNS` Operator to create individual entries for routes. |
| 22 | + |
| 23 | +This tutorial guides you through deploying and configuring the External DNS Operator with a custom domain in {product-title} (ROSA). |
| 24 | + |
| 25 | +[IMPORTANT] |
| 26 | +==== |
| 27 | +The External DNS Operator does not support IRSA/STS and uses long-lived IAM credentials. This tutorial will be updated once STS is supported. |
| 28 | +==== |
| 29 | + |
| 30 | +[id="cloud-experts-external-dns-prerequisites"] |
| 31 | +== Prerequisites |
| 32 | + |
| 33 | +* A ROSA cluster |
| 34 | +* You have access to the OpenShift CLI (`oc`) |
| 35 | +* You have access to the AWS CLI (`aws`) |
| 36 | +* A unique domain, such as *.apps.<company_name>.io |
| 37 | +* An Amazon Route 53 public hosted zone for the above domain |
| 38 | + |
| 39 | +=== Environment setup |
| 40 | + |
| 41 | +* Prepare the environment variables: |
| 42 | ++ |
| 43 | +[source,terminal] |
| 44 | +---- |
| 45 | +$ export DOMAIN=apps.<company_name>.io <1> |
| 46 | +$ export AWS_PAGER="" |
| 47 | +$ export CLUSTER_NAME=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}" | sed 's/-[a-z0-9]\{5\}$//') |
| 48 | +$ export REGION=$(oc get infrastructure cluster -o=jsonpath="{.status.platformStatus.aws.region}") |
| 49 | +$ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) |
| 50 | +$ export SCRATCH="/tmp/${CLUSTER_NAME}/external-dns" |
| 51 | +$ mkdir -p ${SCRATCH} |
| 52 | +$ echo "Cluster: ${CLUSTER_NAME}, Region: ${REGION}, AWS Account ID: ${AWS_ACCOUNT_ID}" |
| 53 | +---- |
| 54 | +<1> The custom domain. |
| 55 | + |
| 56 | +[id="cloud-experts-external-dns-custom-domain-setup"] |
| 57 | +== Custom domain setup |
| 58 | + |
| 59 | +ROSA manages secondary Ingress Controllers using the Custom Domain Operator. The following procedure outlines how to deploy a secondary Ingress Controller using a custom domain. |
| 60 | + |
| 61 | +.Prerequisites |
| 62 | + |
| 63 | +* A unique domain, such as `*.apps.<company_name>.io` |
| 64 | +* A custom SAN or wildcard certificate, such as `CN=*.apps.<company_name>.io` |
| 65 | + |
| 66 | +.Procedure |
| 67 | + |
| 68 | +. Create a new project |
| 69 | ++ |
| 70 | +[source,terminal] |
| 71 | +---- |
| 72 | +$ oc new-project external-dns-operator |
| 73 | +---- |
| 74 | + |
| 75 | +. Create a new TLS secret from a private key and a public certificate, where `fullchain.pem` is your full wildcard certificate chain (including any intermediaries) and `privkey.pem` is your wildcard certificate's private key. |
| 76 | ++ |
| 77 | +.Example |
| 78 | +[source,terminal] |
| 79 | +---- |
| 80 | +$ oc -n external-dns-operator create secret tls external-dns-tls --cert=fullchain.pem --key=privkey.pem |
| 81 | +---- |
| 82 | + |
| 83 | +. Create a new `CustomDomain` custom resource (CR): |
| 84 | ++ |
| 85 | +.Example `external-dns-custom-domain.yaml` |
| 86 | +[source,yaml] |
| 87 | +---- |
| 88 | +apiVersion: managed.openshift.io/v1alpha1 |
| 89 | +kind: CustomDomain |
| 90 | +metadata: |
| 91 | + name: external-dns |
| 92 | +spec: |
| 93 | + domain: apps.<company_name>.io <1> |
| 94 | + scope: External |
| 95 | + loadBalancerType: NLB |
| 96 | + certificate: |
| 97 | + name: external-dns-tls |
| 98 | + namespace: external-dns-operator |
| 99 | +---- |
| 100 | +<1> The custom domain. |
| 101 | + |
| 102 | +. Apply the CR: |
| 103 | ++ |
| 104 | +.Example |
| 105 | +[source,terminal] |
| 106 | +---- |
| 107 | +$ oc apply -f external-dns-custom-domain.yaml |
| 108 | +---- |
| 109 | + |
| 110 | +. Verify that your custom domain Ingress Controller has been deployed and is `Ready`: |
| 111 | ++ |
| 112 | +[source,terminal] |
| 113 | +---- |
| 114 | +$ oc get customdomains |
| 115 | +---- |
| 116 | ++ |
| 117 | +.Example output |
| 118 | +[source,terminal] |
| 119 | +---- |
| 120 | +NAME ENDPOINT DOMAIN STATUS |
| 121 | +external-dns xxrywp.<company_name>.cluster-01.opln.s1.openshiftapps.com *.apps.<company_name>.io Ready |
| 122 | +---- |
| 123 | + |
| 124 | +[id="cloud-experts-external-dns-prepare-aws-account"] |
| 125 | +== Prepare AWS account |
| 126 | + |
| 127 | +. Retrieve the Amazon Route 53 public hosted zone ID: |
| 128 | ++ |
| 129 | +[source,terminal] |
| 130 | +---- |
| 131 | +$ export ZONE_ID=$(aws route53 list-hosted-zones-by-name --output json \ |
| 132 | + --dns-name "${DOMAIN}." --query 'HostedZones[0]'.Id --out text | sed 's/\/hostedzone\///') |
| 133 | +---- |
| 134 | ++ |
| 135 | +. Create an AWS IAM Policy document that allows the External DNS Operator to update _only_ the custom domain public hosted zone: |
| 136 | ++ |
| 137 | +[source,terminal] |
| 138 | +---- |
| 139 | +$ cat << EOF > "${SCRATCH}/external-dns-policy.json" |
| 140 | +{ |
| 141 | + "Version": "2012-10-17", |
| 142 | + "Statement": [ |
| 143 | + { |
| 144 | + "Effect": "Allow", |
| 145 | + "Action": [ |
| 146 | + "route53:ChangeResourceRecordSets" |
| 147 | + ], |
| 148 | + "Resource": [ |
| 149 | + "arn:aws:route53:::hostedzone/${ZONE_ID}" |
| 150 | + ] |
| 151 | + }, |
| 152 | + { |
| 153 | + "Effect": "Allow", |
| 154 | + "Action": [ |
| 155 | + "route53:ListHostedZones", |
| 156 | + "route53:ListResourceRecordSets" |
| 157 | + ], |
| 158 | + "Resource": [ |
| 159 | + "*" |
| 160 | + ] |
| 161 | + } |
| 162 | + ] |
| 163 | +} |
| 164 | +EOF |
| 165 | +---- |
| 166 | ++ |
| 167 | +. Create an AWS IAM policy: |
| 168 | ++ |
| 169 | +[source,terminal] |
| 170 | +---- |
| 171 | +$ export POLICY_ARN=$(aws iam create-policy --policy-name "${CLUSTER_NAME}-AllowExternalDNSUpdates" \ |
| 172 | + --policy-document file://${SCRATCH}/external-dns-policy.json \ |
| 173 | + --query 'Policy.Arn' --output text) |
| 174 | +---- |
| 175 | ++ |
| 176 | +. Create an AWS IAM user: |
| 177 | ++ |
| 178 | +[source,terminal] |
| 179 | +---- |
| 180 | +$ aws iam create-user --user-name "${CLUSTER_NAME}-external-dns-operator" |
| 181 | +---- |
| 182 | +. Attach the policy: |
| 183 | ++ |
| 184 | +[source,terminal] |
| 185 | +---- |
| 186 | +$ aws iam attach-user-policy --user-name "${CLUSTER_NAME}-external-dns-operator" --policy-arn $POLICY_ARN |
| 187 | +---- |
| 188 | ++ |
| 189 | +[NOTE] |
| 190 | +==== |
| 191 | +This will be changed to STS using IRSA in the future. |
| 192 | +==== |
| 193 | +. Create AWS keys for the IAM user: |
| 194 | ++ |
| 195 | +[source,terminal] |
| 196 | +---- |
| 197 | +$ SECRET_ACCESS_KEY=$(aws iam create-access-key --user-name "${CLUSTER_NAME}-external-dns-operator") |
| 198 | +---- |
| 199 | +. Create static credentials: |
| 200 | ++ |
| 201 | +[source,terminal] |
| 202 | +---- |
| 203 | +$ cat << EOF > "${SCRATCH}/credentials" |
| 204 | +[default] |
| 205 | +aws_access_key_id = $(echo $SECRET_ACCESS_KEY | jq -r '.AccessKey.AccessKeyId') |
| 206 | +aws_secret_access_key = $(echo $SECRET_ACCESS_KEY | jq -r '.AccessKey.SecretAccessKey') |
| 207 | +EOF |
| 208 | +---- |
| 209 | + |
| 210 | +[id="cloud-experts-external-dns-install-external-dns-operator"] |
| 211 | +== Install the External DNS Operator |
| 212 | + |
| 213 | +. Install the External DNS Operator from OperatorHub: |
| 214 | ++ |
| 215 | +[source,terminal] |
| 216 | +---- |
| 217 | +$ cat << EOF | oc apply -f - |
| 218 | +apiVersion: operators.coreos.com/v1 |
| 219 | +kind: OperatorGroup |
| 220 | +metadata: |
| 221 | + name: external-dns-group |
| 222 | + namespace: external-dns-operator |
| 223 | +spec: |
| 224 | + targetNamespaces: |
| 225 | + - external-dns-operator |
| 226 | +--- |
| 227 | +apiVersion: operators.coreos.com/v1alpha1 |
| 228 | +kind: Subscription |
| 229 | +metadata: |
| 230 | + name: external-dns-operator |
| 231 | + namespace: external-dns-operator |
| 232 | +spec: |
| 233 | + channel: stable-v1.1 |
| 234 | + installPlanApproval: Automatic |
| 235 | + name: external-dns-operator |
| 236 | + source: redhat-operators |
| 237 | + sourceNamespace: openshift-marketplace |
| 238 | +EOF |
| 239 | +---- |
| 240 | ++ |
| 241 | +. Wait until the External DNS Operator is running: |
| 242 | ++ |
| 243 | +[source,terminal] |
| 244 | +---- |
| 245 | +$ oc rollout status deploy external-dns-operator --timeout=300s |
| 246 | +---- |
| 247 | ++ |
| 248 | +. Create a secret from the AWS IAM user credentials: |
| 249 | ++ |
| 250 | +[source,terminal] |
| 251 | +---- |
| 252 | +$ oc -n external-dns-operator create secret generic external-dns \ |
| 253 | + --from-file "${SCRATCH}/credentials" |
| 254 | +---- |
| 255 | +. Deploy the ExternalDNS controller: |
| 256 | ++ |
| 257 | +[source,terminal] |
| 258 | +---- |
| 259 | +$ cat << EOF | oc apply -f - |
| 260 | +apiVersion: externaldns.olm.openshift.io/v1beta1 |
| 261 | +kind: ExternalDNS |
| 262 | +metadata: |
| 263 | + name: ${DOMAIN} |
| 264 | +spec: |
| 265 | + domains: |
| 266 | + - filterType: Include |
| 267 | + matchType: Exact |
| 268 | + name: ${DOMAIN} |
| 269 | + provider: |
| 270 | + aws: |
| 271 | + credentials: |
| 272 | + name: external-dns |
| 273 | + type: AWS |
| 274 | + source: |
| 275 | + openshiftRouteOptions: |
| 276 | + routerName: external-dns |
| 277 | + type: OpenShiftRoute |
| 278 | + zones: |
| 279 | + - ${ZONE_ID} |
| 280 | +EOF |
| 281 | +---- |
| 282 | +. Wait until the controller is running: |
| 283 | ++ |
| 284 | +[source,terminal] |
| 285 | +---- |
| 286 | +$ oc rollout status deploy external-dns-${DOMAIN} --timeout=300s |
| 287 | +---- |
| 288 | + |
| 289 | +[id="cloud-experts-external-dns-deploy-a-sample-application"] |
| 290 | +== Deploy a sample application |
| 291 | + |
| 292 | +. Create a new project for our sample application: |
| 293 | ++ |
| 294 | +[source,terminal] |
| 295 | +---- |
| 296 | +$ oc new-project hello-world |
| 297 | +---- |
| 298 | ++ |
| 299 | +. Deploy a hello world application: |
| 300 | ++ |
| 301 | +[source,terminal] |
| 302 | +---- |
| 303 | +$ oc new-app -n hello-world --image=docker.io/openshift/hello-openshift |
| 304 | +---- |
| 305 | ++ |
| 306 | +. Create a route for the application specifying your custom domain name: |
| 307 | ++ |
| 308 | +.Example |
| 309 | ++ |
| 310 | +[source,terminal] |
| 311 | +---- |
| 312 | +$ oc -n hello-world create route edge --service=hello-openshift hello-openshift-tls \ |
| 313 | +--hostname hello-openshift.${DOMAIN} |
| 314 | +---- |
| 315 | +. Check if the DNS record was created automatically by ExternalDNS: |
| 316 | ++ |
| 317 | +[NOTE] |
| 318 | +==== |
| 319 | +It can take a few minutes for the record to appear in Amazon Route 53. |
| 320 | +==== |
| 321 | ++ |
| 322 | +[source,terminal] |
| 323 | +---- |
| 324 | +$ aws route53 list-resource-record-sets --hosted-zone-id ${ZONE_ID} \ |
| 325 | + --query "ResourceRecordSets[?Type == 'CNAME']" | grep hello-openshift |
| 326 | +---- |
| 327 | +. You can also view the TXT records that indicate they were created by ExternalDNS: |
| 328 | ++ |
| 329 | +[source,terminal] |
| 330 | +---- |
| 331 | +$ aws route53 list-resource-record-sets --hosted-zone-id ${ZONE_ID} \ |
| 332 | + --query "ResourceRecordSets[?Type == 'TXT']" | grep ${DOMAIN} |
| 333 | +---- |
| 334 | +. Navigate to your custom console domain in the browser where you see the OpenShift login. |
| 335 | ++ |
| 336 | +[source,terminal] |
| 337 | +---- |
| 338 | +$ echo console.${DOMAIN} |
| 339 | +---- |
0 commit comments