03 · Kubernetes Service (IKS) Basics¶
The instance group in Module 2 auto-scales identical VSIs, but it has no idea what's running on each one beyond "boot this image." IBM Cloud Kubernetes Service (IKS) is the managed alternative for anything container-based: you describe workloads declaratively, and Kubernetes schedules, restarts, and scales the containers themselves, on top of worker nodes that are still VPC VSIs under the hood.
VPC clusters vs. classic clusters¶
IKS has two cluster types: classic (IBM's older, non-VPC networking) and VPC (worker nodes are VSIs inside a VPC you control, like the one from Module 1). New clusters should always be VPC clusters — classic exists mainly for infrastructure provisioned years ago. This module uses VPC clusters exclusively.
ibmcloud ks cluster create vpc-gen2 \
--name mastery-iks \
--vpc-id $(ibmcloud is vpc ha-app-vpc --output json | jq -r .id) \
--subnet-id $(ibmcloud is subnet private-subnet-z1 --output json | jq -r .id) \
--zone us-south-1 \
--flavor bx2.4x16 \
--workers 2 \
--resource-group-id $(ibmcloud resource group mastery-path --output json | jq -r .[0].id)
Gotcha: cluster creation takes 20-40 minutes for the master alone — much longer than any Level 1 resource. Don't assume something's stuck; poll status instead of re-running the create command (which will just fail with "cluster already exists").
Worker pools across zones¶
A worker pool is a set of nodes with the same flavor and zone. For resilience, add a pool spanning a second zone rather than putting every node in one:
ibmcloud ks worker-pool create vpc-gen2 \
--cluster mastery-iks \
--name pool-z2 \
--vpc-id $(ibmcloud is vpc ha-app-vpc --output json | jq -r .id) \
--subnet-id $(ibmcloud is subnet private-subnet-z2 --output json | jq -r .id) \
--zone us-south-2 \
--flavor bx2.4x16 \
--size-per-zone 2
Kubernetes' scheduler spreads pods across nodes on its own, but it only spreads across zones if the worker pools themselves span zones — a single-zone cluster is a single point of failure no matter how many replicas a Deployment specifies.
Configure kubectl¶
ibmcloud ks cluster config --cluster mastery-iks
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# 10.10.1.4 Ready <none> 12m v1.29.x+IKS
# 10.10.17.5 Ready <none> 3m v1.29.x+IKS
ibmcloud ks cluster config merges the cluster's credentials into
~/.kube/config — it doesn't print them, it configures kubectl directly,
which is easy to forget if you're expecting console output.
Deploy a workload¶
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-web
spec:
replicas: 3
selector:
matchLabels:
app: hello-web
template:
metadata:
labels:
app: hello-web
spec:
containers:
- name: hello-web
image: icr.io/mastery-path/hello-web:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
kubectl apply -f deployment.yaml
kubectl get pods -o wide
# hello-web-... spread across both zones' nodes automatically
Always set resources.requests — the scheduler uses requests to decide
which node has room for a pod; without them, Kubernetes can over-pack a
node and every pod on it starves under load simultaneously.
Expose it: Service and Ingress¶
A Deployment alone isn't reachable. A Service gives it a stable
in-cluster (or external) address:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: hello-web
spec:
type: LoadBalancer
selector:
app: hello-web
ports:
- port: 80
targetPort: 8080
kubectl apply -f service.yaml
kubectl get service hello-web
# EXTERNAL-IP shows a VPC load balancer hostname once provisioned
type: LoadBalancer on IKS provisions an Application Load Balancer for
VPC behind the scenes — the same resource type from Module 2, just
managed by Kubernetes instead of by hand. For multiple services sharing one
public entry point, use an Ingress resource with the cluster's built-in
ALB Ingress controller instead of one LoadBalancer Service per app (each
LoadBalancer Service provisions its own billable ALB).
Push your own image to IBM Cloud Container Registry¶
ibmcloud cr region-set us-south
ibmcloud cr namespace-add mastery-path
docker build -t icr.io/mastery-path/hello-web:1.0 .
docker push icr.io/mastery-path/hello-web:1.0
# Confirm it landed, and check for known vulnerabilities
ibmcloud cr images --restrict mastery-path
ibmcloud cr image-scan icr.io/mastery-path/hello-web:1.0
IKS worker nodes can pull from Container Registry in the same account and
region without extra image-pull secrets, as long as the cluster's default
service account has the Reader role on the registry namespace (granted
automatically for clusters and registries in the same account).
How It Actually Works¶
- IKS runs the control plane (API server, etcd, scheduler, controller
manager) as IBM-managed infrastructure you never see or pay VSI cost
for, while worker nodes are ordinary VPC VSIs that IBM provisions,
joins to the cluster via kubelet, and bills to your account like any
other compute. That split is exactly why
ibmcloud ks cluster createtakes minutes (standing up managed control-plane components) whileibmcloud ks worker-pool createis comparatively fast — it's provisioning VSIs and running a join script against a control plane that already exists. kubectl applydoesn't imperatively create your Deployment — it writes desired state into etcd via the API server, and the Deployment controller (running in the control plane) is what actually notices the diff and creates ReplicaSets and Pods to reconcile reality toward it, on a continuous watch-and-reconcile loop rather than a one-shot action; that's why a Pod youkubectl deletemanually comes right back — the controller re-notices the drift within its next reconciliation pass.- The scheduler assigns each Pod to a node using a two-phase process — filtering out nodes that fail hard constraints (insufficient CPU/memory requests, taints, zone/anti-affinity rules), then scoring the survivors and picking the best fit — which is the actual mechanism behind pods landing across zones in a multi-zone worker pool: it's an explicit spread-scoring heuristic, not a round-robin default.
- A
LoadBalancerService doesn't run its own proxy — creating one triggers IKS's cloud-controller-manager to provision a real VPC Application Load Balancer via the VPC API, and Kubernetes' own kube-proxy on each node programs iptables/IPVS rules that route traffic arriving at the node to the correct backend Pod. Two separate load-balancing layers are actually involved: the VPC ALB distributing across nodes, and kube-proxy distributing from a node to whichever Pod is currently scheduled there.
Cheat sheet¶
| Command | Purpose |
|---|---|
ibmcloud ks cluster create vpc-gen2 --vpc-id <id> --subnet-id <id> --zone <z> --flavor <f> --workers <n> |
Create a VPC-based cluster |
ibmcloud ks worker-pool create vpc-gen2 --cluster <c> --zone <z> --subnet-id <id> |
Add a worker pool in another zone |
ibmcloud ks cluster config --cluster <c> |
Merge cluster credentials into kubectl |
kubectl apply -f <file> |
Create/update resources from YAML |
kubectl get pods -o wide |
List pods and the node each landed on |
kubectl get service <name> |
Show a Service's external address |
ibmcloud cr namespace-add <ns> / docker push icr.io/<ns>/<image> |
Push a private image |
ibmcloud ks cluster rm --cluster <c> |
Delete the cluster (and its worker nodes) |
Exercise¶
Create a two-zone IKS cluster with one worker pool per zone, deploy the
hello-web Deployment with 3 replicas, and expose it with a LoadBalancer
Service. Confirm with kubectl get pods -o wide that replicas landed on
nodes in both zones, then kubectl delete node <one-node-name> is not
the way to test failover on a managed cluster — instead cordon and drain
one node (kubectl cordon <node> then kubectl drain <node>
--ignore-daemonsets) and confirm the Deployment's pods reschedule onto the
remaining nodes. Delete the cluster with ibmcloud ks cluster rm when
done — worker nodes bill hourly for as long as the cluster exists.