Multi-container orchestration

In this section, we'll revisit our ticketing service: a kiosk web service as a frontend that provides an interface for get/put tickets. There is a Redis acting as cache to manage how many tickets we have. Redis also acts as a publisher/subscriber channel. Once a ticket is sold, the kiosk will publish an event into it. The subscriber is called recorder and will write a timestamp and record it to the MySQL database. Please refer to the last section in Chapter 2, DevOps with Containers, for a detailed Dockerfile and Docker compose implementation. We'll use Deployment, Service, Secret, Volume, and ConfigMap objects to implement this example in Kubernetes. The source code can be found at the following link: https://github.com/DevOps-with-Kubernetes/examples/tree/master/chapter3/3-3_kiosk.

The service architecture with Kubernetes resources is shown in the following diagram:

An example of a kiosk in the Kubernetes world

We'll need four kinds of pods. Deployment is the best choice to manage or deploy the pods. This will reduce the effort required when we carry out deployments in the future thanks to its deployment strategy feature. Since the kiosk, Redis, and MySQL will be accessed by other components, we'll associate services to their pods. MySQL acts as a datastore and, for simplicity, we'll mount a local volume to it. Note that Kubernetes offers a bunch of choices. Check out the details and examples in Chapter 4, Managing Stateful Workloads. We'll want to store sensitive information such as our MySQL root and user password in secrets. The other insensitive configuration, such as the DB name or username, we'll leave to ConfigMap.

We'll launch MySQL first, as the recorder depends on it. Before creating MySQL, we'll have to create a corresponding secret and ConfigMap first. To create a secret, we need to generate base64 encrypted data:

// generate base64 secret for MYSQL_PASSWORD and MYSQL_ROOT_PASSWORD
# echo -n "pass" | base64
cGFzcw==
# echo -n "mysqlpass" | base64
bXlzcWxwYXNz

Then, we're able to create the secret:

# cat secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-user
type: Opaque
data:
password: cGFzcw==
---
# MYSQL_ROOT_PASSWORD
apiVersion: v1
kind: Secret
metadata:
name: mysql-root
type: Opaque
data:
password: bXlzcWxwYXNz
// create mysql secret
# kubectl create -f secret.yaml --record
secret "mysql-user" created
secret "mysql-root" created

After that, we come to our ConfigMap. Here, we put the database user and the database name as an example:

# cat config.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: mysql-config
data:
user: user
database: db
// create ConfigMap
# kubectl create -f config.yaml --record
configmap "mysql-config" created

It's then time to launch MySQL and its service:

# cat mysql.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: lmysql
spec:
replicas: 1
selector:
matchLabels:
tier: database
version: "5.7"
template:
metadata:
labels:
tier: database
version: "5.7"
spec:
containers:
- name: lmysql
image: mysql:5.7
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-vol
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root
key: password
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
name: mysql-config
key: database
- name: MYSQL_USER
valueFrom:
configMapKeyRef:
name: mysql-config
key: user
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-user
key: password
volumes:
- name: mysql-vol
hostPath:
path: /mysql/data
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
---
kind: Service
apiVersion: v1
metadata:
name: lmysql-service
spec:
selector:
tier: database
ports:
- protocol: TCP
port: 3306
targetPort: 3306
name: tcp3306

We can put more than one spec into a file by adding three dashes as separation. Here, we mount hostPath /mysql/data into pods with the path /var/lib/mysql. In the environment section, we use the secret and ConfigMap syntax with secretKeyRef and configMapKeyRef.

After creating MySQL, Redis would be the next best candidate, since other services are dependent on it but it doesn't have any prerequisites:

// create Redis deployment 
# cat redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: lcredis
spec:
replicas: 1
selector:
matchLabels:
tier: cache
version: "3.0"
template:
metadata:
labels:
tier: cache
version: "3.0"
spec:
containers:
- name: lcredis
image: redis:3.0
ports:
- containerPort: 6379
minReadySeconds: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
---
kind: Service
apiVersion: v1
metadata:
name: lcredis-service
spec:
selector:
tier: cache
ports:
- protocol: TCP
port: 6379
targetPort: 6379
name: tcp6379

// create redis deployements and service
# kubectl create -f redis.yaml
deployment "lcredis" created
service "lcredis-service" created

It would then be a good time to start the kiosk:

# cat kiosk-example.yaml
apiVersion: apps/v1

kind: Deployment
metadata:
name: kiosk-example
spec:
replicas: 5
selector:
matchLabels:
tier: frontend
version: "3"
template:
metadata:
labels:
tier: frontend
version: "3"
annotations:
maintainer: cywu
spec:
containers:
- name: kiosk-example
image: devopswithkubernetes/kiosk-example
ports:
- containerPort: 5000
env:
- name: REDIS_HOST
value: lcredis-service.default
minReadySeconds: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
---
kind: Service
apiVersion: v1
metadata:
name: kiosk-service
spec:
type: NodePort
selector:
tier: frontend
ports:
- protocol: TCP
port: 80
targetPort: 5000
name: tcp5000
// launch the spec
# kubectl create -f kiosk-example.yaml
deployment "kiosk-example" created
service "kiosk-service" created

Here, we expose lcredis-service.default to environment variables to kiosk pods. This is the DNS name that kube-dns creates for Service objects (referred to as Services in this chapter). Hence, the kiosk can access the Redis host via environment variables.

In the end, we'll create a recorder. This doesn't expose any interface to others, so it doesn't need a Service object:

# cat recorder-example.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: recorder-example
spec:
replicas: 3
selector:
matchLabels:
tier: backend
version: "3"
template:
metadata:
labels:
tier: backend
version: "3"
annotations:
maintainer: cywu
spec:
containers:
- name: recorder-example
image: devopswithkubernetes/recorder-example
env:
- name: REDIS_HOST
value: lcredis-service.default
- name: MYSQL_HOST
value: lmysql-service.default
- name: MYSQL_USER
value: root
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root
key: password
minReadySeconds: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1

// create recorder deployment
# kubectl create -f recorder-example.yaml
deployment "recorder-example" created

The recorder needs to access both Redis and MySQL. It uses root credentials that are injected via a secret. Both endpoints for Redis and MySQL are accessed via a service DNS name, <service_name>.<namespace>.

We could then check the Deployment objects:

// check deployment details
# kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
kiosk-example 5 5 5 5 1h
lcredis 1 1 1 1 1h
lmysql 1 1 1 1 1h
recorder-example 3 3 3 3 1h

As expected, we have four Deployment objects with a different desired count of pods.

As we expose the kiosk as a NodePort, we should be able to access its service endpoint and see whether it works properly. Assume we have a node, the IP of which is 192.168.99.100, and the NodePort that Kubernetes allocates is 30520.

If you're using minikube, minikube service [-n NAMESPACE] [--url] NAME could help you access service NodePort via your default browser:

// open kiosk console

# minikube service kiosk-service

Opening kubernetes service default/kiosk-service in default browser...

This will allow us to find out the IP and the port.

We could then create and get a ticket using POST and GET /tickets:

// post ticket
# curl -XPOST -F 'value=100' http://192.168.99.100:30520/tickets
SUCCESS
// get ticket
# curl -XGET http://192.168.99.100:30520/tickets
100
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset