This project demonstrates the practical implementation of Kubernetes Headless Services using a real-world example of MySQL master-slave replication. The demo consists of three main components: a Write Application, a Read Application, and a MySQL database setup with master-slave replication.
A Headless Service in Kubernetes is a service that doesn't allocate a cluster IP address. Instead, it creates DNS entries for each Pod that's part of the service. This is particularly useful when:
- You need direct communication with specific Pods
- You don't need load balancing
- You want to discover individual Pod IP addresses
Key characteristics of a Headless Service:
- The
clusterIPfield is set toNone - DNS queries return the IP addresses of all Pods that match the service selector
- Each Pod gets its own DNS entry in the format:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
This project demonstrates Headless Services using a practical MySQL master-slave replication setup. By using this common database pattern, we'll see how Headless Services enable direct Pod communication.
- Direct Communication Need: Write operations must go to the master node specifically
- DNS-Based Discovery: Slave nodes need to find and connect to the master
- Stable Network Identity: Each MySQL instance needs a consistent identity for replication
- 2 Pod replicas (1 master, 1 slave)
- mysql-0.mysql: Master node (Primary)
- mysql-1.mysql: Slave node (Replica)
- Headless Service (mysql): For direct Pod access
- ClusterIP Service (mysql-read): For read operations load balancing
- Write App: Connects directly to master (mysql-0.mysql)
- Read App: Connects to mysql-read service
- DNS entries:
- mysql-0.mysql.default.svc.cluster.local
- mysql-1.mysql.default.svc.cluster.local
- Used by Write App to connect to master
- No load balancing (clusterIP: None)
- Regular ClusterIP service
- Load balances read requests across both Pods
- Used by Read App for distributed reading
- StatefulSet with 2 replicas
- Persistent storage: 1Gi per pod
- Master node configuration:
- Binary logging enabled
- Handles all write operations
- Slave node configuration:
- Super-read-only mode
- Automatic replication from master
- Connects directly to MySQL master via headless service
- Features:
- User registration interface
- Database initialization
- Environment Configuration:
- DB_HOST: mysql-0.mysql
- DB_USER: appuser
- DB_PASSWORD: password123
- DB_NAME: userdb
- Connects to mysql-read service for load balanced reads
- Features:
- MySQL pod identification
- Load balancing demonstration
- Environment Configuration:
- DB_HOST: mysql-read
- DB_USER: appuser
- DB_PASSWORD: password123
- DB_NAME: userdb
For the MySQL StatefulSet to work properly, you need a functioning storage class for dynamic volume provisioning. This demo uses Persistent Volume Claims (PVCs) which require either manually created Persistent Volumes or a storage provisioner.
OpenEBS provides an easy way to set up dynamic volume provisioning for local volumes, which is perfect for development and testing environments.
- Add the OpenEBS Helm repository:
helm repo add openebs https://openebs.github.io/charts
helm repo update- Install OpenEBS (without Mayastor for simplicity):
helm install openebs --namespace openebs openebs/openebs \
--set engines.replicated.mayastor.enabled=false \
--create-namespace- Verify the installation:
kubectl get pods -n openebs- Check your current storage classes:
kubectl get storageclass- Make OpenEBS hostpath the default storage class:
# First, unset the default flag on your current default storage class (if any)
kubectl patch storageclass <current-default-storage-class> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
# Then set OpenEBS hostpath as the default
kubectl patch storageclass openebs-hostpath -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'- Verify the changes:
kubectl get storageclassYou should see openebs-hostpath marked as default (with "(default)" next to its name).
- Update your PVCs to use the new storage class: If you've already defined PVCs in your YAML files, you can either:
- Remove the
storageClassNamefield to use the default - Explicitly set
storageClassName: openebs-hostpath
If you're running in a cloud environment:
- AWS: Use
aws-ebsstorage class - GCP: Use
standardstorage class - Azure: Use
managed-premiumstorage class
For production use cases, consider using more robust storage solutions like Ceph, Portworx, or cloud-native volume solutions.
- Kubernetes cluster
- kubectl configured
- Node ports 30000 and 30001 available
- Storage class configured (see Dynamic Volume Provisioning section)
/
├── kubernetes/
│ ├── mysql-statefulset.yaml
│ ├── write-app.yaml
│ └── read-app.yaml
├── write-app/
│ ├── app.py
│ ├── index.html
│ ├── requirements.txt
│ └── Dockerfile
└── read-app/
├── app.py
├── index.html
├── requirements.txt
└── Dockerfile
- Clone the repository:
git clone https://github.com/OpqTech/kubernetes-headless-project.git
cd kubernetes-headless-project- Deploy MySQL Cluster:
kubectl apply -f kubernetes/mysql-statefulset.yaml- Deploy Applications:
kubectl apply -f kubernetes/write-app.yaml
kubectl apply -f kubernetes/read-app.yaml- Verify all pods are running:
kubectl get pods# Check DNS entries created by headless service
kubectl run -it --rm debug --image=busybox -- nslookup mysql
kubectl run -it --rm debug --image=busybox -- nslookup mysql-0.mysql- Access Write App:
http://<node-ip>:30000- Writes always go to mysql-0 (master) via headless service
- Access Read App:
http://<node-ip>:30001- Reads are load balanced across pods
# Connect to master (mysql-0)
kubectl exec -it mysql-0 -- mysql -u root -e "SHOW SLAVE HOSTS;"
# Connect to slave (mysql-1)
kubectl exec -it mysql-1 -- mysql -u root -e "SHOW SLAVE STATUS\G"The slave status should show:
- Slave_IO_Running: Yes
- Slave_SQL_Running: Yes
-
Headless vs Regular Services
- Headless: Direct Pod access, no load balancing
- ClusterIP: Load balanced, single service IP
-
DNS Resolution
- Headless creates individual Pod DNS entries
- Regular creates single service DNS entry
-
Use Cases
- When to use headless services
- When to use regular services
- How to combine both in a single application
This is a demo environment. For production:
- Use proper secrets for MySQL credentials
- Configure appropriate storage classes
- Implement proper backup strategies
