In an effort to get more fluent with Kubernetes, I’m using it instead of Docker/Docker compose during my local development. I’m using Docker Desktop on a Mac, so it’s going to be slightly different than minikube or on Windows.
In most cases I can take a Docker Compose file and recreate it as a services and deployments that my code can interact with. But in the case of Kafka, there are a couple complexities that I needed to iron out to get it running. The two main problems were exposing the Kafka UI web app from inside the cluster, and exposing Kafka in a way that both Kafka UI and applications running outside the cluster (maybe from an IDE or command-line) can access topics.
The first problem is to expose Kafka both inside and outside the cluster. Inside the cluster it gets used by Kafka UI, and outside the cluster it gets used by the code I’m creating. The trick is in both the Service and Deployment definitions. In the Service definition, I set the type to NodePort, but I define ports for both internal and external access, with only the external access using a nodeport:
apiVersion: v1
kind: Service
metadata:
labels:
app: kafka-broker
name: kafka-service
namespace: dev
spec:
type: NodePort
ports:
- port: 9092
name: inner
targetPort: 9092
protocol: TCP
- port: 30092
name: outer
targetPort: 30092
nodePort: 30092
protocol: TCP
selector:
app: kafka-broker
Setting up the broker is more complex. First, we make an alias for PLAINTEXT:
- name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP value: "INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT"
Then, make sure the container is exposed on two ports:
ports:
- containerPort: 9092
- containerPort: 30092
Port 9092 is the standard Kafka port, and is used inside the cluster. Port 30092 is in the accepted range for nodeports, and is the port used outside the cluster. Now, we tell Kafka to listen on those ports using the aliases, and advertise listeners both inside the cluster and using the domain we initially defined:
- name: KAFKA_INTER_BROKER_LISTENER_NAME
value: "INSIDE"
- name: KAFKA_LISTENERS
value: "INSIDE://:9092,OUTSIDE://:30092"
- name: KAFKA_ADVERTISED_LISTENERS
value: "INSIDE://$(KAFKA_SERVICE_SERVICE_HOST):9092,OUTSIDE://kafka.example.net:30092"
Here’s the full Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: kafka-broker
name: kafka-broker
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: kafka-broker
template:
metadata:
labels:
app: kafka-broker
spec:
hostname: kafka-broker
containers:
- env:
- name: KAFKA_BROKER_ID
value: "1"
- name: KAFKA_ZOOKEEPER_CONNECT
value: $(ZOOKEEPER_SERVICE_SERVICE_HOST):2181
- name: KAFKA_INTER_BROKER_LISTENER_NAME
value: "INSIDE"
- name: KAFKA_LISTENERS
value: "INSIDE://:9092,OUTSIDE://:30092"
- name: KAFKA_ADVERTISED_LISTENERS
value: "INSIDE://$(KAFKA_SERVICE_SERVICE_HOST):9092,OUTSIDE://kafka.example.net:30092"
- name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
value: "INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT"
- name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR
value: "1"
- name: KAFKA_TRANSACTION_STATE_LOG_MIN_ISR
value: "1"
- name: KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR
value: "1"
image: confluentinc/cp-kafka:7.6.0
imagePullPolicy: IfNotPresent
name: kafka-broker
ports:
- containerPort: 9092
- containerPort: 30092
The next problem, exposing Kafka UI, is not unique to Kafka, but is a problem for any web app you run inside the Docker Desktop cluster. I didn’t want to use port forwarding. While I know that works, it’s not as convenient as just accessing a URL.
The first thing I set up was URL resolution in my hosts
file. I edited my /etc/hosts
file and added the following line:
127.0.0.1 kafka.example.net
I have taken to usingexample.net
for my cluster resources, and example.com
for my Docker Compose resources. Both those domains are reserved and won’t conflict with actual domains.
The next step is to install an nginx ingress into my local cluster:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
Now I can set up Kafka UI:
apiVersion: v1
kind: Service
metadata:
labels:
app: kafka-ui-service
name: kafka-ui-service
namespace: dev
spec:
ports:
- name: kafka-ui-port
port: 80
targetPort: 8080
selector:
app: kafka-ui
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: kafka-ui
name: kafka-ui
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: kafka-ui
template:
metadata:
labels:
app: kafka-ui
spec:
hostname: kafka-ui
containers:
- env:
- name: KAFKA_CLUSTERS_0_NAME
value: "local"
- name: KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS
value: $(KAFKA_SERVICE_SERVICE_HOST):9092
image: provectuslabs/kafka-ui:master
imagePullPolicy: IfNotPresent
name: kafka-ui
ports:
- containerPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kafka-ui-ingress
namespace: dev
spec:
ingressClassName: nginx
rules:
- host: kafka.example.net
http:
paths:
- backend:
service:
name: kafka-ui-service
port:
number: 80
path: /
pathType: Prefix
I’ve placed thecomplete yaml file in a repo, and this post sent me in the right direction for my solution.
Top comments (0)