Your first deployment
This page walks the whole way once: container image in, working HTTPS address out. It assumes kubectl is set up and your default namespace is set, see Access and kubectl.
The conventions for routes, listener names and certificates are documented under Gateway API. Here they are applied, not explained a second time.
The manifest
Three objects, one file. Save it as app.yaml and replace two things: put your own namespace in the hostname, and your own image in the container.
Your namespace already owns every name under <your-namespace>.itsh.dev, so myapp.<your-namespace>.itsh.dev works immediately with no DNS setup. Free-tier namespaces use <your-namespace>.itsh-apps.dev instead. Serving a domain of your own is a separate step, see Gateway API.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: registry.example.com/myapp:1.0.0
ports:
- name: http
containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp
annotations:
cert-manager.io/cluster-issuer: "auto"
spec:
parentRefs:
- name: default
namespace: nginx-gateway
sectionName: https-myapp-your-namespace-itsh-dev
hostnames:
- myapp.<your-namespace>.itsh.dev
rules:
- backendRefs:
- name: myapp
port: 80Four details in there are not optional extras:
- Set your requests deliberately. They decide how much capacity is reserved for your pod, and they are what you are billed on, see Billing and quotas. Leaving them out does not fail: your namespace fills in a small default instead, currently 50m CPU and 64Mi of memory, with limits of 200m and 256Mi. That is rarely what your application wants, it is billed all the same, and a later HPA has no useful baseline. Set the numbers rather than inheriting them.
- The
securityContextkeeps your first apply quiet. Without it Kubernetes prints four warnings about privilege escalation, capabilities, root and seccomp. They are warnings rather than rejections, so the pod still starts, but the settings are worth having and the noise is not. One caveat:runAsNonRoot: trueneeds an image that actually runs as a non-root user. If yours does not, drop that one line and keep the other three, which still apply and still remove three of the four warnings. - Probes separate "running" from "able to answer requests". Without a readiness probe the service sends traffic to pods that are still starting. Adjust the paths to what your application actually serves.
sectionNamehas to match the hostname. Take the hostname and replace every dot with a dash, somyapp.tenant-7.itsh.devgiveshttps-myapp-tenant-7-itsh-dev. Get that wrong and your route attaches to the wrong listener, or to none at all.
Your workloads run in a sandboxed runtime, and manifests that break the platform's rules are rejected before they ever reach the cluster. Which rules those are is under Permissions and policies.
Apply it
kubectl apply -f app.yamlkubectl get pods
kubectl rollout status deployment/myapprollout status only returns once every replica is ready. If it just sits there, that is already half the diagnosis: no pod is becoming ready. Cancel with Ctrl+C and work through the troubleshooting below.
DNS
Nothing to do. Your namespace subdomain already points at the gateway, so the hostname in the manifest above resolves as soon as the route exists.
This only becomes a step when you bring your own domain, which is covered under Gateway API.
When something does not come up
kubectl describe pod <name> is the first move in all three cases: the events at the end of the output almost always name the reason.
ImagePullBackOff: the image could not be pulled. For a private registry the credentials are missing. Create a secret of typekubernetes.io/dockerconfigjsonand reference it underimagePullSecretsin the pod template. For a public registry, check the spelling and the tag.CrashLoopBackOff: the container starts and exits immediately.kubectl logs deployment/myappshows why. Once the pod has already restarted you need the previous attempt:kubectl logs deployment/myapp --previous.Pending: the pod cannot be placed. Almost always your namespace quota is exhausted.kubectl get resourcequotashows what is left, and Billing and quotas explains what feeds into that quota in the first place.
What's next
- Gateway API for routing, hostnames and custom domains
- Billing and quotas for what your requests cost
- Troubleshooting if it did not come up