From 41774e8e266b3132fc94a21533185232999ebc01 Mon Sep 17 00:00:00 2001 From: Richard Wall Date: Tue, 3 Sep 2024 21:02:09 +0100 Subject: [PATCH] AWS tutorial Signed-off-by: Richard Wall --- content/docs/getting-started/README.md | 11 + content/docs/manifest.json | 6 +- .../getting-started-aws-letsencrypt/README.md | 387 ++++++++++++++++++ .../certificate.yaml | 19 + .../clusterissuer-selfsigned.yaml | 7 + .../deployment.yaml | 37 ++ .../service.yaml | 14 + .../amazon_elastic_kubernetes_service.svg | 7 + 8 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 content/docs/tutorials/getting-started-aws-letsencrypt/README.md create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml create mode 100644 public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml create mode 100644 public/images/icons/amazon_elastic_kubernetes_service.svg diff --git a/content/docs/getting-started/README.md b/content/docs/getting-started/README.md index c358360f498..fdfff463b95 100644 --- a/content/docs/getting-started/README.md +++ b/content/docs/getting-started/README.md @@ -34,3 +34,14 @@ description: Quick start guides for cert-manager title="Let's Encrypt" /> Learn how to deploy cert-manager on **Azure Kubernetes Service (AKS)** and how to configure it to get certificates for an HTTPS web server, from **Let's Encrypt**. + + + Amazon Elastic Kubernetes Services icon + Let's Encrypt icon 292Jacob, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via Wikimedia Commons + Learn how to deploy cert-manager on **Amazon Elastic Kubernetes Service (EKS)** and how to configure it to get certificates for an HTTPS web server, from **Let's Encrypt**. + diff --git a/content/docs/manifest.json b/content/docs/manifest.json index d8f5c0c3aa6..a9a340d8d54 100644 --- a/content/docs/manifest.json +++ b/content/docs/manifest.json @@ -18,7 +18,7 @@ { "title": "Supported Releases", "path": "/docs/releases/README.md" - }, + }, { "title": "1.15", "path": "/docs/releases/release-notes/release-notes-1.15.md" @@ -598,6 +598,10 @@ "title": "AKS + LoadBalancer + Let's Encrypt", "path": "/docs/tutorials/getting-started-aks-letsencrypt/README.md" }, + { + "title": "AWS + LoadBalancer + Let's Encrypt", + "path": "/docs/tutorials/getting-started-aws-letsencrypt/README.md" + }, { "title": "Migrating from Kube-LEGO", "path": "/docs/tutorials/acme/migrating-from-kube-lego.md" diff --git a/content/docs/tutorials/getting-started-aws-letsencrypt/README.md b/content/docs/tutorials/getting-started-aws-letsencrypt/README.md new file mode 100644 index 00000000000..b1a9db06ca9 --- /dev/null +++ b/content/docs/tutorials/getting-started-aws-letsencrypt/README.md @@ -0,0 +1,387 @@ +--- +title: Deploy cert-manager on Amazon Elastic Kubernetes (EKS) and use Let's Encrypt to sign a certificate for an HTTPS website +description: | + Learn how to deploy cert-manager on Amazon Elastic Kubernetes Service (EKS) + and configure it to get a signed certificate from Let's Encrypt for an HTTPS web server, + using the DNS-01 protocol and Amazon Route53 DNS with workload identity federation. +--- + +*Last Verified: 3 September 2024* + +In this tutorial you will learn how to deploy and configure cert-manager on Amazon Elastic Kubernetes Service (EKS) +and how to deploy an HTTPS web server and make it available on the Internet. +You will learn how to configure cert-manager to get a signed certificate from Let's Encrypt, +which will allow clients to connect to your HTTPS website securely. +You will configure cert-manager to use the [Let's Encrypt DNS-01 challenge protocol](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) with Azure DNS, +using workload identity federation to authenticate to Amazon Route53. + +> **Amazon Web Services**: A suite of cloud computing services by Amazon.
+> **Kubernetes**: Runs on your servers. Automates the deployment, scaling, and management of containerized applications.
+> **cert-manager**: Runs in Kubernetes. Obtains TLS / SSL certificates and ensures the certificates are valid and up-to-date.
+> **Let’s Encrypt**: An Internet service. Allows you to generate free short-lived SSL certificates. + +# Part 1 + +In the first part of this tutorial you will learn the basics required to deploy an HTTPS website on an Amazon Elastic Kubernetes Service (EKS) cluster, using cert-manager to create the SSL certificate for the web server. +You will create a DNS domain for your website, create an EKS cluster, install cert-manager, create an SSL certificate and then deploy a web server which responds to HTTPS requests from clients on the Internet. +But the SSL certificate in part 1 is only for testing purposes. + +In part 2 you will learn how to configure cert-manager to use Let's Encrypt and Route53 DNS to create a trusted SSL certificate which you can use in production. + +## Configure the AWS CLI (`aws`) + +If your have not already done so, [download and install the AWS CLI (`aws`)](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). + +Set up the `aws` command for interactive use: + +```bash +aws configure +``` + +Set the default resource group and location: + +```bash +export AWS_DEFAULT_REGION=us-west-2 # ❗ Your AWS region. +``` + +> πŸ“– Read +> [Set up the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html), and +> [Configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html), +> to learn more about configuring `aws`. + +## Create a public domain name + +In this tutorial you will deploy an HTTPS website with a publicly accessible domain name, so you will need to register a domain unless you already have one. +You could use any [domain name registrar](https://www.cloudflare.com/en-gb/learning/dns/glossary/what-is-a-domain-name-registrar/) to register a domain name for your site. +For example you could use `Gandi` and register a cheap domain name for the purposes of this tutorial. + +Now that you know your domain name, save it in an environment variable: + +```bash +export DOMAIN_NAME=example.com # ❗ Replace this with your own DNS domain name +``` + +And add it to AWS Route53 as a zone: + +```bash +aws route53 create-hosted-zone --caller-reference $(uuidgen) --name $DOMAIN_NAME +``` + +The details of the created zone will be printed to the console: + +```json +{ + "Location": "https://route53.amazonaws.com/2013-04-01/hostedzone/Z0984294TRL0R8AT3SQA", + "HostedZone": { + "Id": "/hostedzone/Z0984294TRL0R8AT3SQA", + "Name": "cert-manager-aws-tutorial.richard-gcp.jetstacker.net.", + "CallerReference": "77274711-b648-4da5-81b7-74512897d0db", + "Config": { + "PrivateZone": false + }, + "ResourceRecordSetCount": 2 + }, + "ChangeInfo": { + "Id": "/change/C04685872DX6N6587E1TL", + "Status": "PENDING", + "SubmittedAt": "2024-09-03T16:29:11.960000+00:00" + }, + "DelegationSet": { + "NameServers": [ + "ns-1504.awsdns-60.org", + "ns-538.awsdns-03.net", + "ns-278.awsdns-34.com", + "ns-1765.awsdns-28.co.uk" + ] + } +} +``` + +Log in to the control panel for your domain registrar and set the NS records for your domain to match the DNS names of the [authoritative DNS servers](https://www.cloudflare.com/en-gb/learning/dns/dns-server-types/) for your Route53 hosted zone. +See `NameServers` in the output of `aws route53 create-hosted-zone` (above) or you can find the name servers later: + +```bash +HOSTED_ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name $DOMAIN_NAME --query "HostedZones[0].Id" --output text) +aws route53 get-hosted-zone --id ${HOSTED_ZONE_ID} +``` + +You can check that the NS records have been updated using `dig` to "trace" the hierarchy of NS records, +rather than using your local DNS resolver: + +```bash +dig $DOMAIN_NAME ns +trace +nodnssec +``` + +> ⏲ It **may** take more than 1 hour for the NS records to be updated in the parent zone, +> and it may take some time for the old NS records to be replaced in the caches of DNS resolver servers, +> if you looked up the DNS name before updating the NS records. +> +> πŸ“– Read [How do I Update My DNS Records?](https://docs.gandi.net/en/domain_names/common_operations/dns_records.html) in the `Gandi.net` docs, +> or seek the equivalent documentation for your own domain name registrar. + +## Create a Kubernetes cluster + +To get started, let's create a Kubernetes cluster using Amazon EKS. +The easiest way to create an EKS cluster is with `eksctl`. +[Download and install `eksctl`](https://eksctl.io/installation/). + +Pick a name for your cluster and save it in an environment variable: + +```bash +export CLUSTER=test-cluster-1 +``` + +Now, create the cluster using the following command: + +```bash +eksctl create cluster \ + --name $CLUSTER \ + --nodegroup-name node-group-1 \ + --node-type t3.small \ + --nodes 3 \ + --nodes-min 1 \ + --nodes-max 3 \ + --managed \ + --spot +``` + +This will update your `kubectl` config file with the credentials for your new cluster. + +Check that you can connect to the cluster: + +```bash +kubectl get nodes -o wide +``` + +> ⏲ It will take 15-20 minutes to create the cluster. Why? +> See [Reduction in EKS cluster creation time](https://github.com/aws/containers-roadmap/issues/1227). +> +> πŸ’΅ To minimize your cloud bill, this command creates a 3-node cluster using +> [low cost virtual machines](https://aws.amazon.com/ec2/instance-types/t3) and +> [spot instances](https://eksctl.io/usage/spot-instances/). +> +> ⚠️ This cluster is only suitable for learning purposes it is not suitable for production use. + +## Install cert-manager + +Now you can install and configure cert-manager. + +Install cert-manager using `helm` as follows: + +```bash +helm install cert-manager cert-manager \ + --repo https://charts.jetstack.io \ + --namespace cert-manager \ + --create-namespace \ + --set crds.enabled=true +``` + +This will create three Deployments and some Services and Pods in a new namespace called `cert-manager`. +It also installs various cluster scoped supporting resources such as RBAC roles and Custom Resource Definitions. + +You can view some of the resources that have been installed as follows: + +```bash +kubectl -n cert-manager get all +``` + +And you can explore the Custom Resource Definitions (cert-manager's API) using `kubectl explain`, as follows: + +```bash +kubectl explain Certificate +kubectl explain CertificateRequest +kubectl explain Issuer +``` + +> πŸ“– Read about [other ways to install cert-manager](../../installation/README.md). +> +> πŸ“– Read more about [Certificates and Issuers](../../concepts/README.md). + +## Create a test ClusterIssuer and a Certificate + +Now everything is ready for you to create your first certificate. +This will be a self-signed certificate but later we'll replace it with a Let's Encrypt signed certificate. + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml +``` +πŸ”— `clusterissuer-selfsigned.yaml` + +```bash +kubectl apply -f clusterissuer-selfsigned.yaml +``` + +Then use `envsubst` to substitute your chosen domain name into the following Certificate template: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml +``` +πŸ”— `certificate.yaml` + +```bash +envsubst < certificate.yaml | kubectl apply -f - +``` + +> πŸ”— If you don't already have `envsubst` installed you can [download and install a Go implementation of `envsubst`](https://github.com/a8m/envsubst). + +Use `cmctl status certificate` to check the status of the Certificate: + +```bash +cmctl status certificate www +``` + +If successful, the private key and the signed certificate will be stored in a Secret called `www-tls`. +You can use `cmctl inspect secret www-tls` to decode the base64 encoded X.509 content of the Secret: + +```terminal +$ cmctl inspect secret www-tls +... +Valid for: + DNS Names: + - www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net + URIs: + IP Addresses: + Email Addresses: + Usages: + - digital signature + - key encipherment + - server auth +... +``` + +## Deploy a sample web server + +Now deploy a simple web server which responds to HTTPS requests with "hello world!". +The SSL / TLS key and certificate are supplied to the web server by using the `www-tls` Secret as a volume +and by mounting its contents into the file system of the `hello-app` container in the Pod: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml +``` +πŸ”— `deployment.yaml` + +```bash +kubectl apply -f deployment.yaml +``` + +You also need to create a Kubernetes LoadBalancer Service, so that connections from the Internet can be routed to the web server Pod. +When you create the following Kubernetes Service, an AWS classic load balancer with an ephemeral public IP address will also be created: + +```yaml file=../../../../public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml +``` +πŸ”— `service.yaml` + +```bash +kubectl apply -f service.yaml +``` + +Within 2-3 minutes, a load balancer should have been provisioned with a public IP. + +```bash +kubectl get service helloweb +``` + +Sample output + +```terminal +$ kubectl get service helloweb +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +helloweb LoadBalancer 10.100.175.247 ae25d292150aa4e3e90e6c25376f9a7d-496307726.us-west-2.elb.amazonaws.com 443:32184/TCP 6m +``` + +The `EXTERNAL-IP` will be different for you and it may be different each time you re-create the LoadBalancer service, +but it will have a stable DNS host name associated with it. + +> ℹ️ By default, EKS creates [classic](https://aws.amazon.com/elasticloadbalancing/features/#Product_comparisons) +> load balancers for LoadBalancer Services in the cluster, +> using the [Legacy Cloud Provider Load balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html#lbc-legacy). +> This is convenient for this tutorial because it does not require any additional software or configuration, but +> [AWS Cloud Provider Load balancer Controller is legacy and is currently only receiving critical bug fixes](https://aws.github.io/aws-eks-best-practices/networking/loadbalancing/loadbalancing/#choosing-load-balancer-type) +> according to the Amazon's [EKS Best Practices Guide](https://aws.github.io/aws-eks-best-practices/). +> Consider using the [AWS Load Balancer Controller](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html) instead. + +The stable DNS host name of the load balancer can be used as an alias for the `www` record in your chosen `$DOMAIN_NAME` +by creating a Route53 [Alias Record](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-choosing-alias-non-alias.html): + +```bash +HOSTED_ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name $DOMAIN_NAME --query "HostedZones[0].Id" --output text) +ELB_CANONICAL_HOSTED_ZONE_NAME=$(kubectl get svc helloweb --output=jsonpath='{ .status.loadBalancer.ingress[0].hostname }') +aws elb describe-load-balancers --query "LoadBalancerDescriptions[?CanonicalHostedZoneName == '$ELB_CANONICAL_HOSTED_ZONE_NAME'] | [0]" \ +| jq '{ + "Comment": "Creating a CNAME record", + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "www.\($DOMAIN_NAME)", + "Type": "A", + "AliasTarget": { + "HostedZoneId": .CanonicalHostedZoneNameID, + "DNSName": .CanonicalHostedZoneName, + "EvaluateTargetHealth": false + } + } + } + ] +}' \ + --arg DOMAIN_NAME "${DOMAIN_NAME}" \ +| aws route53 change-resource-record-sets --hosted-zone-id $HOSTED_ZONE_ID --change-batch file:///dev/stdin +``` + +> ℹ️ Read [Routing traffic to an ELB load balancer](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-elb-load-balancer.html) +> to learn more about this task. +> +> ℹ️ The script uses a [`JMESPath` query](https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-filter.html) +> to get the ELB for the Kubernetes Service by matching against the DNS name. + +Check that `www.$DOMAIN_NAME` now resolves to the ephemeral public IP address of the load balancer: + +```terminal +$ dig www.$DOMAIN_NAME A +... +;; QUESTION SECTION: +;www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. IN A + +;; ANSWER SECTION: +www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. 60 IN A 34.212.236.229 +www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. 60 IN A 44.232.234.71 +www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net. 60 IN A 35.164.69.198 +``` + +If the DNS is correct and the load balancer is working and the hello world web server is running, +you should now be able to connect to it using curl or using your web browser: + +```bash +curl --insecure -v https://www.$DOMAIN_NAME +``` + +> ⚠️ We used curl's `--insecure` option because it rejects self-signed certificates by default. +> Later you will learn how to create a trusted certificate signed by Let's Encrypt. + +You should see that the certificate has the expected DNS names and that it is self-signed: + +```terminal +... +* Server certificate: +* subject: CN=www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net +* start date: Sep 4 08:43:56 2024 GMT +* expire date: Dec 3 08:43:56 2024 GMT +* issuer: CN=www.cert-manager-aws-tutorial.richard-gcp.jetstacker.net +* SSL certificate verify result: self-signed certificate (18), continuing anyway. +... +Hello, world! +Protocol: HTTP/2.0! +Hostname: helloweb-55cb4cd887-tjlvh +``` + +> πŸ“– Read more about [Using a Service to Expose Your App](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/). + +# Part 2 + +In part 1 you created a test certificate. +Now you will learn how to configure cert-manager to use Let's Encrypt and Amazon Route53 DNS to create a trusted certificate which you can use in production. +You need to prove to Let's Encrypt that you own the domain name of the certificate and one way to do this is to create a special DNS record in that domain. +This is known as the [DNS-01 challenge type](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge). + +cert-manager can create that DNS record for you in by using the Amazon Route53 API but it needs to authenticate first, +and currently the most secure method of authentication is to use [IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). +The advantages of this method are that cert-manager will use an ephemeral Kubernetes ServiceAccount Token to authenticate to AWS and the token need not be stored in a Kubernetes Secret. + +> πŸ“– Read about [other ways to configure the ACME issuer with Amazon Route53 DNS](../../configuration/acme/dns01/route53.md). diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml new file mode 100644 index 00000000000..8b7fc837ab1 --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/certificate.yaml @@ -0,0 +1,19 @@ +# certificate.yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: www +spec: + secretName: www-tls + privateKey: + rotationPolicy: Always + commonName: www.$DOMAIN_NAME + dnsNames: + - www.$DOMAIN_NAME + usages: + - digital signature + - key encipherment + - server auth + issuerRef: + name: selfsigned + kind: ClusterIssuer diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml new file mode 100644 index 00000000000..b3072419f80 --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/clusterissuer-selfsigned.yaml @@ -0,0 +1,7 @@ +# clusterissuer-selfsigned.yaml +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned +spec: + selfSigned: {} diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml new file mode 100644 index 00000000000..a16dc865efc --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/deployment.yaml @@ -0,0 +1,37 @@ +# deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: helloweb + labels: + app: hello +spec: + selector: + matchLabels: + app: hello + tier: web + template: + metadata: + labels: + app: hello + tier: web + spec: + containers: + - name: hello-app + image: us-docker.pkg.dev/google-samples/containers/gke/hello-app-tls:1.0 + imagePullPolicy: Always + ports: + - containerPort: 8443 + volumeMounts: + - name: tls + mountPath: /etc/tls + readOnly: true + env: + - name: TLS_CERT + value: /etc/tls/tls.crt + - name: TLS_KEY + value: /etc/tls/tls.key + volumes: + - name: tls + secret: + secretName: www-tls diff --git a/public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml b/public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml new file mode 100644 index 00000000000..ffac52325ba --- /dev/null +++ b/public/docs/tutorials/getting-started-aws-letsencrypt/service.yaml @@ -0,0 +1,14 @@ +# service.yaml +apiVersion: v1 +kind: Service +metadata: + name: helloweb +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 8443 + selector: + app: hello + tier: web + type: LoadBalancer diff --git a/public/images/icons/amazon_elastic_kubernetes_service.svg b/public/images/icons/amazon_elastic_kubernetes_service.svg new file mode 100644 index 00000000000..6bb9db41091 --- /dev/null +++ b/public/images/icons/amazon_elastic_kubernetes_service.svg @@ -0,0 +1,7 @@ + + + Icon-Resource/Containers/Res_Amazon-Elastic-Kubernetes-Service_EKS-on-Outposts_48 + + + + \ No newline at end of file