k3sm licensed apache-2.0 (DCO)

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 with EADDRINUSE. 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 cpu limit 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-http from 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 :53 uses 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

Next#

  • nodeport reaches the same workload from the Mac’s own interfaces.
  • probes covers when a replica should receive traffic.