Deployments and services
Run a native workload under a Deployment, front it with a ClusterIP Service, and reach it through the userspace proxy.
Fifteen minutes gets you a Deployment of a native Darwin workload, a ClusterIP Service in front of
it, and a successful curl against the Service VIP from your Mac.
Both objects are the upstream ones, and the Deployment controller and the scheduler are genuine Kubernetes components. Underneath, the replicas are processes and the Service is a userspace proxy rather than iptables.
1. The Deployment#
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-http
namespace: default
labels:
app: hello-http
spec:
replicas: 1
selector:
matchLabels:
app: hello-http
template:
metadata:
labels:
app: hello-http
spec:
nodeSelector:
kubernetes.io/os: darwin
tolerations:
- key: k3sm.io/provider
operator: Exists
effect: NoSchedule
containers:
- name: hello-http
image: native
command:
- /bin/sh
- -c
- |
while :; do
printf 'HTTP/1.1 200 OK\r\nContent-Length: 6\r\nConnection: close\r\n\r\nhello\n' \
| /usr/bin/nc -l 8080 >/dev/null 2>&1 || sleep 1
done
ports:
- name: http
containerPort: 8080
resources:
requests:
memory: 32Mi
limits:
memory: 128Mi
This is examples/deployment-native.yaml. Apply it and watch the rollout:
k3sm kubectl apply -f deployment-native.yaml
k3sm kubectl rollout status deploy/hello-http
k3sm kubectl get pods -l app=hello-http -o wide
Three details in that manifest matter.
- The Pod template carries the node selector and the toleration. Only DaemonSet-owned Pods get the provider toleration injected, so a Deployment template that omits it produces Pods that are admitted and then never scheduled.
- One replica, because the workload binds a port. macOS has no network namespaces, so every Pod on
a node shares one port space with the node’s own listeners. A second replica of a workload that
binds 8080 lands on the same Mac and its
listen()fails withEADDRINUSE. Scale a port-binding workload by adding nodes, not replicas, or have it bind port 0 and register itself. - A memory limit, and no CPU limit. Memory is sampled and enforced, and an over-limit Pod is
OOM-killed. Darwin has no CFS millicore enforcement, so CPU is best-effort and a
cpulimit would claim a guarantee k3sm cannot keep. See limitations.
2. The ClusterIP Service#
apiVersion: v1
kind: Service
metadata:
name: hello-http
namespace: default
labels:
app: hello-http
spec:
type: ClusterIP
selector:
app: hello-http
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
This is examples/service-clusterip.yaml.
k3sm kubectl apply -f service-clusterip.yaml
k3sm kubectl get svc hello-http
There is no kube-proxy, no iptables, and no netns on macOS. The ClusterIP is a VIP that k3sm’s
userspace proxy listens on, so the address in kubectl get svc works from this Mac and from Pods on
it:
curl "http://$(k3sm kubectl get svc hello-http -o jsonpath='{.spec.clusterIP}')/"
The response is the hello the workload writes. The demo responder handles one connection per loop
iteration, so if a request lands mid-loop, retry rather than concluding the Service is broken.
3. Two Caveats#
- Resolve the VIP, not the name. In-Pod cluster-DNS wiring is not live yet, and a Pod’s resolver
still defers to the host resolver, so
http://hello-httpfrom inside a Pod is not something to depend on today. Dial the ClusterIP instead; limitations covers this DNS gap. - Services are TCP only. General UDP Services are unimplemented, for ClusterIP and NodePort
alike, and only cluster DNS on
:53uses UDP. A UDP port on a Service is admitted with a warning and then blackholes.
4. Clean Up#
k3sm kubectl delete -f service-clusterip.yaml -f deployment-native.yaml