Auto-update blog content from Obsidian: 2025-08-19 12:11:03
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 14s
Blog Deployment / Test-Staging (push) Successful in 4s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 4s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 5s
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 14s
Blog Deployment / Test-Staging (push) Successful in 4s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 4s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 5s
This commit is contained in:
@@ -358,7 +358,8 @@ helm install ingress-nginx \
|
|||||||
--repo=https://kubernetes.github.io/ingress-nginx \
|
--repo=https://kubernetes.github.io/ingress-nginx \
|
||||||
--namespace=ingress-nginx \
|
--namespace=ingress-nginx \
|
||||||
--create-namespace ingress-nginx \
|
--create-namespace ingress-nginx \
|
||||||
--set controller.ingressClassResource.default=true
|
--set controller.ingressClassResource.default=true \
|
||||||
|
--set controller.config.strict-validate-path-type=false
|
||||||
```
|
```
|
||||||
|
|
||||||
The controller is deployed and exposes a `LoadBalancer` service. In my setup, it picked the second available IP in the BGP range:
|
The controller is deployed and exposes a `LoadBalancer` service. In my setup, it picked the second available IP in the BGP range:
|
||||||
@@ -402,7 +403,6 @@ spec:
|
|||||||
operator: NotIn
|
operator: NotIn
|
||||||
values:
|
values:
|
||||||
- ingress-nginx
|
- ingress-nginx
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
After replacing the previous shared pool with these two, the Ingress Controller got the desired IP `192.168.55.55`, and the `test-lb` service picked `192.168.55.100` as expected:
|
After replacing the previous shared pool with these two, the Ingress Controller got the desired IP `192.168.55.55`, and the `test-lb` service picked `192.168.55.100` as expected:
|
||||||
@@ -465,7 +465,7 @@ Test Ingress on HTTP
|
|||||||
---
|
---
|
||||||
## Secure Connection with TLS
|
## Secure Connection with TLS
|
||||||
|
|
||||||
Exposing services over HTTP works, but in practice we almost always want to use **HTTPS**. That’s where TLS certificates comes in, it encrypts traffic between clients and your cluster, ensuring security and trust.
|
Exposing services over plain HTTP is fine for testing, but in practice we almost always want **HTTPS**. TLS certificates encrypt traffic and provides authenticity and trust to users.
|
||||||
|
|
||||||
### Cert-Manager
|
### Cert-Manager
|
||||||
|
|
||||||
@@ -485,15 +485,152 @@ helm install cert-manager jetstack/cert-manager \
|
|||||||
|
|
||||||
#### Setup Cert-Manager
|
#### Setup Cert-Manager
|
||||||
|
|
||||||
verify clusterissuer
|
Next, we configure a **ClusterIssuer** for Let’s Encrypt. This resource tells Cert-Manager how to request certificates:
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-staging
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
email: <email>
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt--key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
ingressClassName: nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
ℹ️ Here I define the **staging** Let’s Encrypt ACME server for testing purposes. Staging certificates are not trusted by browsers, but they prevent hitting Let’s Encrypt’s strict rate limits during development.
|
||||||
|
|
||||||
|
Apply it:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f clusterissuer.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify if your `ClusterIssuer` is `Ready`:
|
||||||
|
```bash
|
||||||
|
kubectl get clusterissuers.cert-manager.io
|
||||||
|
NAME READY AGE
|
||||||
|
letsencrypt-staging True 14m
|
||||||
|
```
|
||||||
|
|
||||||
|
If it doesn’t become `Ready`, use `kubectl describe` on the resource to troubleshoot.
|
||||||
|
|
||||||
### Add TLS in an Ingress
|
### Add TLS in an Ingress
|
||||||
|
|
||||||
ingress tls code
|
Now we can secure our service with TLS by adding a `tls` section in the `Ingress` spec and referencing the `ClusterIssuer`:
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: test-ingress-https
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- test.vezpi.me
|
||||||
|
secretName: test-vezpi-me-tls
|
||||||
|
rules:
|
||||||
|
- host: test.vezpi.me
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: test-lb
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
Behind the scenes, Cert-Manager goes through this workflow to issue the certificate:
|
||||||
|
- Detects the `Ingress` with `tls` and the `ClusterIssuer`.
|
||||||
|
- Creates a Certificate CRD that describes the desired cert + Secret storage.
|
||||||
|
- Creates an Order CRD to represent one issuance attempt with Let’s Encrypt.
|
||||||
|
- Creates a Challenge CRD (e.g., HTTP-01 validation).
|
||||||
|
- Provisions a temporary solver Ingress/Pod to solve the challenge.
|
||||||
|
- Creates a CertificateRequest CRD and sends the CSR to Let’s Encrypt.
|
||||||
|
- Receives the signed certificate and stores it in a Kubernetes Secret.
|
||||||
|
- The Ingress automatically uses the Secret to serve HTTPS.
|
||||||
|
|
||||||
|
|
||||||
|
✅ Once this process completes, your Ingress is secured with a TLS certificate.
|
||||||
|

|
||||||
|
|
||||||
|
### Switch to Production Certificates
|
||||||
|
|
||||||
|
Once staging works, we can safely switch to the **production** ACME server to get a trusted certificate from Let’s Encrypt:
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: <email>
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
ingressClassName: nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the `Ingress` to reference the new `ClusterIssuer`:
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: test-ingress-https
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- test.vezpi.me
|
||||||
|
secretName: test-vezpi-me-tls
|
||||||
|
rules:
|
||||||
|
- host: test.vezpi.me
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: test-lb
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the staging certificate is still stored in the Secret, I delete it to trigger a fresh request against production:
|
||||||
|
```bash
|
||||||
|
kubectl delete secret test-vezpi-me-tls
|
||||||
|
```
|
||||||
|
|
||||||
|
🎉 My `Ingress` is now secured with a valid TLS certificate from Let’s Encrypt. Requests to `https://test.vezpi.me` are encrypted end-to-end and routed by the NGINX Ingress Controller to my `nginx` pod:
|
||||||
|

|
||||||
|
|
||||||
verify
|
|
||||||
|
|
||||||
---
|
---
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
|
In this journey, I started from the basics, exposing a single pod with a `LoadBalancer` service, and step by step built a production-ready setup:
|
||||||
|
- Learned about **Kubernetes Services** and their different types.
|
||||||
|
- Used **BGP with Cilium** and OPNsense to assign external IPs directly from my network.
|
||||||
|
- Introduced **Ingress** to scale better, exposing multiple services through a single entry point.
|
||||||
|
- Installed the **NGINX Ingress Controller** to handle routing.
|
||||||
|
- Automated certificate management with **Cert-Manager**, securing my services with Let’s Encrypt TLS certificates.
|
||||||
|
|
||||||
|
🚀 The result: my pod is now reachable at a real URL, secured with HTTPS, just like any modern web application.
|
||||||
|
|
||||||
|
This is a huge milestone in my homelab Kubernetes journey, in the next article, I'd like to explore persistent storage to be able to use my **Ceph** cluster on **Proxmox**.
|
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Reference in New Issue
Block a user