Goal: Enable HTTPS for nginx.apps.lan using a self-signed certificate, store it as a Kubernetes TLS Secret, configure Traefik to use it, and force HTTP→HTTPS redirects. All automation is done from the control host via Ansible; kubectl and helm run on the k3s master.
Reference environment: Kubuntu 25 host (fullstacklab.site) with SSH user stackadmin; k3s master 192.168.56.10, namespace apps (from Day 5), NGINX release web (from Day 6).
Step 1 — Create a self-signed cert and TLS Secret
We generate a one-year RSA cert with SAN and save it as nginx-tls in the apps namespace. The playbook includes a fallback for OpenSSL versions without -addext.
---
# ansible/tls_secret_on_master.yml
- name: Create self-signed cert and K8s TLS secret for nginx.apps.lan
hosts: k3s_master
become: true
vars:
kubeconfig: "/etc/rancher/k3s/k3s.yaml"
tls_dir: "/tmp/tls-nginx-apps-lan"
cn: "nginx.apps.lan"
secret_name: "nginx-tls"
namespace: "apps"
environment:
KUBECONFIG: "{{ kubeconfig }}"
tasks:
- name: Ensure openssl is installed
package:
name: openssl
state: present
- name: Ensure temp dir exists
file:
path: "{{ tls_dir }}"
state: directory
mode: "0755"
- name: Try create self-signed cert with -addext (RSA 2048, 365d)
shell: |
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout {{ tls_dir }}/{{ cn }}.key \
-out {{ tls_dir }}/{{ cn }}.crt \
-subj "/CN={{ cn }}" \
-addext "subjectAltName = DNS:{{ cn }}"
args:
creates: "{{ tls_dir }}/{{ cn }}.crt"
register: openssl_addext
failed_when: false
- name: Fallback via openssl config (for systems without -addext)
when: openssl_addext.rc != 0
block:
- name: Write minimal openssl config with SAN
copy:
dest: "{{ tls_dir }}/san.cnf"
mode: "0644"
content: |
[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[ req_distinguished_name ]
CN = {{ cn }}
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = {{ cn }}
- name: Create self-signed cert using config
shell: |
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout {{ tls_dir }}/{{ cn }}.key \
-out {{ tls_dir }}/{{ cn }}.crt \
-config {{ tls_dir }}/san.cnf \
-extensions v3_req
args:
creates: "{{ tls_dir }}/{{ cn }}.crt"
- name: Create/Update TLS secret
shell: |
kubectl -n {{ namespace }} create secret tls {{ secret_name }} \
--cert={{ tls_dir }}/{{ cn }}.crt \
--key={{ tls_dir }}/{{ cn }}.key \
--dry-run=client -o yaml | kubectl apply -f -
Step 2 — Traefik middleware for HTTP→HTTPS redirect
---
# ansible/traefik_https_middleware.yml
- name: Create Traefik HTTPS redirect middleware in apps ns
hosts: k3s_master
become: true
vars:
kubeconfig: "/etc/rancher/k3s/k3s.yaml"
namespace: "apps"
mw_name: "https-redirect"
environment:
KUBECONFIG: "{{ kubeconfig }}"
tasks:
- name: Apply Middleware manifest
command: kubectl apply -f -
args:
stdin: |
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: {{ mw_name }}
namespace: {{ namespace }}
spec:
redirectScheme:
scheme: https
permanent: true
Step 3 — Helm upgrade: enable TLS + attach the middleware
Using a values file avoids brittle quoting for annotation keys that include slashes or dots.
---
# ansible/helm_upgrade_tls.yml
- name: Helm upgrade NGINX ingress with TLS and HTTPS redirect
hosts: k3s_master
become: true
vars:
kubeconfig: "/etc/rancher/k3s/k3s.yaml"
release_name: "web"
namespace: "apps"
ingress_host: "nginx.apps.lan"
tls_secret: "nginx-tls"
environment:
KUBECONFIG: "{{ kubeconfig }}"
tasks:
- name: Write Helm values (TLS + Traefik middleware)
copy:
dest: /tmp/web-values.yaml
mode: "0644"
content: |
service:
type: ClusterIP
ingress:
enabled: true
ingressClassName: traefik
hostname: {{ ingress_host }}
tls: true
extraTls:
- hosts:
- {{ ingress_host }}
secretName: {{ tls_secret }}
annotations:
"traefik.ingress.kubernetes.io/router.middlewares": "apps-https-redirect@kubernetescrd"
# Optional pinning if specific image is required:
# - name: Append image pin
# blockinfile:
# path: /tmp/web-values.yaml
# insertafter: EOF
# block: |
# image:
# repository: bitnamilegacy/nginx
# tag: 1.29.1-debian-12-r0
- name: Helm upgrade with values
command: >
helm upgrade --install {{ release_name }} bitnami/nginx -n {{ namespace }}
-f /tmp/web-values.yaml
- name: Wait for deployment availability
command: kubectl -n {{ namespace }} rollout status deploy/{{ release_name }}-nginx --timeout=180s
- name: Show ingress details
command: kubectl -n {{ namespace }} get ingress {{ release_name }}-nginx -o yaml
Step 4 — Validate from the control host
---
# ansible/https_validate_from_host.yml
- name: Validate HTTPS and redirect from control host
hosts: localhost
connection: local
gather_facts: false
vars:
url_http: "http://nginx.apps.lan/"
url_https: "https://nginx.apps.lan/"
tasks:
- name: Ensure /etc/hosts entry exists
become: true
lineinfile:
path: /etc/hosts
create: true
state: present
regexp: '^\S+\s+nginx\.apps\.lan\s*$'
line: "192.168.56.10 nginx.apps.lan"
- name: Check HTTP head (expect 301/308 to https)
command: curl -I {{ url_http }}
- name: Check HTTPS content (self-signed, so -k)
command: curl -k -s {{ url_https }} | head -n 5
What’s next: swap the self-signed cert for mkcert (trusted locally) or try Let’s Encrypt (ACME staging) with Traefik. We’ll also practice Helm upgrade/rollback flow with a custom index page.