K8s Learning: Step 06 — Deploy the Application
06 — Deploy the Application
Takes the deploy template, substitutes env vars, copies to VM, applies it. Creates (or updates) three Kubernetes resources. Your app goes live with HTTPS.
Resource 1: The Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: blog-farewell-cloud
spec:
replicas: 1
selector:
matchLabels:
app: blog-farewell-cloud
template:
metadata:
labels:
app: blog-farewell-cloud
spec:
containers:
- name: blog-farewell-cloud
image: docker.io/library/blog-farewell-cloud:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
volumeMounts:
- name: externaldata
mountPath: /externaldata
volumes:
- name: externaldata
hostPath:
path: /externaldata/say.farewell.cloud
type: DirectoryOrCreate
- Labels & selectors — Deployment creates pods with label
app: blog-farewell-cloud; Service finds them by this label - imagePullPolicy: Never — use locally loaded image
- volumeMounts + hostPath — persistent data on VM filesystem, survives pod restarts
- DirectoryOrCreate — creates the host directory if it doesn't exist
Resource 2: The Service
apiVersion: v1
kind: Service
metadata:
name: blog-farewell-cloud
spec:
selector:
app: blog-farewell-cloud
ports:
- port: 80
targetPort: 8000
Stable internal address. Cluster consumers talk to it on port 80; it forwards to the pod's port 8000. Inside cluster: http://blog-farewell-cloud:80.
Resource 3: The Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog-farewell-cloud
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- say.farewell.cloud
secretName: blog-farewell-cloud-tls
rules:
- host: say.farewell.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog-farewell-cloud
port:
number: 80
cert-manager.io/cluster-issuer— triggers automatic TLStls.secretName— cert-manager stores cert here; Traefik reads itrules— hostname → Service routing
Certificate: First Deploy vs. Subsequent
| Scenario | What Happens |
|---|---|
| First deploy | No Secret exists → ACME challenge → cert obtained (30–90 sec) |
| Subsequent deploys | Secret valid → cert-manager does nothing → instant HTTPS |
| After ~60 days | cert-manager auto-renews, no deploy needed |
The Script Actions
1. Template substitution
DEPLOY_YAML_CONTENT=$(cat deploy_template.yaml)
DEPLOY_YAML_CONTENT=${DEPLOY_YAML_CONTENT//{K3S_UNIQUE_DOCKER_IMAGE_NAME}/$K3S_UNIQUE_DOCKER_IMAGE_NAME}
# ... same for other variables
2. Copy and apply
scp $DEPLOY_YAML_PATH $K3S_SSH_USER@$K3S_SSH_HOSTNAME:~
ssh ... "sudo k3s kubectl apply -f ~/deploy.yaml"
kubectl apply is declarative — "make reality match this YAML."
3. Restart the deployment
ssh ... "sudo k3s kubectl rollout restart deployment/$K3S_UNIQUE_DOCKER_IMAGE_NAME"
Forces a new pod since the tag is always :latest. Old pod terminates only after new one is running (rolling update).