- containers give developers power over their environments - no longer have to worry about the host the application is running on.
- containers bring development and operations together
- without containers applications are generally deployed as monoliths
- with containers we can separate applications into their own services, with their own teams, and own deployments
- open-source platform for orchestrating containers across clusters of machines
- containers are great on their own, but orchestration is what allows one to:
- schedule containers onto nodes
- scale up/down
- roll out udpates
- balance containers across machines
- containers are great on their own, but orchestration is what allows one to:
- based on 10+ years of experience at Google
- simplfies the following:
- deployment
- scaling
- rolling out new features
- load-balancing
- simplfies the following:
- lean
- lightweight
- simple
- accessible
- portable
- public
- private
- hybrid
- multi-cloud
- extensible
- modular
- pluggable
- composable
- self-healing
- auto-placement
- auto-replication
- based on Kubernetes
- containers as a service
- Google Compute Engine
- Docker
- Kubernetes
- containers as a service
- orchestrate and schedule docker containers
- consumes Compute Engine instances and resources
- uses a declarative syntax (using yml) to manage applications
- decouple operational and development concerns
- manages and maintains
- logging
- health management
- monitoring
- scaling
- container cluster
- a group of GCE instances running Kubernetes
- a cluster represents compute networking and storage
- it's a combination of virtual machines, which are GCE instances running K8s, as well as services
- master node
- hosts the REST API
- hosts replication controllers
- is fully managed, and can't be SSH into, but can be accessed via a web UI
- if you were to setup up your own K8s cluster outside of GKE you would be responsible for managing master and all of its nodes
- nodes
- machines where pods are scheduled
- nodes act as K8s workers
- host the Docker runtime
- host a Kubelet agent which master uses to communicate with the nodes
- pods
- a grouping of tightly coupled containers
- pods are scheduled onto nodes
- co-located group of containers that:
- share context
- share the same networking namespace
- expose an IP address
- pods can be thought of as a logical host inside a node which is a physical host
- pods are ephemeral
- they can be destroyed / deleted at any time
- one can dynamically spin up more pods when needed using a replication controller
- labels
- these are user-defined values
- key/value pairs attached to resources, e.g. pods
- provide a way to organise pods
- allow replication controllers and services a way to target pods through selectors
- e.g.
- we can label frontend pods
fe
- we can label backend pods
be
- we can set up microservices with labels
- replication controllers can be reference these labels for scaling pods up and down
- if we need to route certain traffic to our frontends, we have a label to do that
- we can label frontend pods
- e.g.
- replication controllers
- manage and monitor the lifecycle of pods
- when configuring a replication controller we define the number of replicas (pods) across a cluster
- replication controller will ensure that the specified number of pods is always there
- handles scaling up / down
- handles replacing pods that die
- services
- provide stable IP address and DNS names for pods, because as pods are ephemeral, and are coming up and down, they are receiving new names
- services make the ephemeral nature of pods discoverable
- services define a set of pods, and a policy to access them
- provide load-balancing services across the pods they target
-
CLI utility that sends requests to the K8s cluster manager
$ kubectl -h
-
not installed with Cloud SDK. Must be installed separately
-
use
gcloud container
for managing clusters$ gcloud container clusters -h
- create clusters
- delete clusters
- resize clusters (add / remove nodes)
- get credentials
- useful for when managing multiple clusters and we need to point
kubectl
to a particular cluster
- useful for when managing multiple clusters and we need to point
We will build:
- a guestbook application
- with a frontend
- with 3 pods defined through replication controller so that we always have 3 frontends running via 3 nodes
- 1 redis master service where writes will go
- running on 1 pod
- best practise to always use a replication controller, even if there is only going to be 1 pod
- 1 redis slave service where reads will come into
- running workers on 2 pods
- when writes hit master, they will be replicated to our slave and thus workers
Create a guestbook with Redis and PHP
- deployment: - a K8s resource that determines the configuration for a set of replicated pods
- services: - create internal and external load balancers for a set of pods
-
# create a cluster called guestbook that has 3 nodes $ gcloud container clusters create guestbook --num-nodes=3
-
once the cluster is built we can view information about the cluster:
$ gcloud container clusters list $ gcloud container clusters describe guestbook
At this point we have the following configuration:
Data will be stored with Redis. The Redis master accepts writes, reads are done from slave instances.
We need to:
- deploy a Redis master
- create a service allowing the application to communicate with the Redis master to write data
We use redis-master-deployment.yaml
to deploy the Redis master. A deployment is a configuration for a set of replicated pods.
# redis-master-deployment.yaml
apiVersion: extensions/v1beta1
# this is a deployment
kind: Deployment
metadata:
# call this pod 'redis-master'
name: redis-master
spec:
# create only 1 replica of this pod
replicas: 1
template:
metadata:
# services with selectors matching these labels will forward traffic here
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: k8s.gcr.io/redis:e2e # or just image: redis
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
We will have only one pod running an instance of the Redis master.
To deploy the pod using the above configuration:
# create a resource using the configuration found in file (-f) redis-master-deployment.yaml
$ kubectl create -f redis-master-deployment.yaml
We can see the results of the deployment by running kubectl get pods
:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-master-7bd4d6ccfd-rh768 1/1 Running 0 1m
# or get additional information by running the command with the 'wide' output (-o)
# Output formats available are json|yaml|wide|custom-columns
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
redis-master-7bd4d6ccfd-rh768 1/1 Running 0 1m 10.12.0.6 gke-guestbook-default-pool-dbb664fc-qj22
To see logs from a specific pod:
# kubectl logs -f POD_NAME
# get the logs for our new Redis master pod, specifying `follow=false`, which means logs are not streamed
$ kubectl logs -f redis-master-7bd4d6ccfd-rh768
We now have the following running on our cluster:
Without a service, our application won't be able to communicate with the pod running our Redis master. We'll create a service which proxies traffic to our Redis master pod.
# redis-master-service.yaml
apiVersion: v1
# this is a service
kind: Service
metadata:
# with a name of redis-master...?
name: redis-master
labels:
app: redis
role: master
tier: backend
spec:
# route traffic on 6379 to the target port of 6379 of the containers that match the specified selector labels below
ports:
- port: 6379
targetPort: 6379
# These are label selectors.
# They match the labels deployed for the Redis master pod(s)
# This ensures traffic is routed to that pod (or more if replicas was set to more than 1)
selector:
app: redis
role: master
tier: backend
We start the Redis master's service as follows:
$ kubectl create -f redis-master-service.yaml
Once the service is up and running, we can verify it by running kubectl get service
:
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.15.240.1 <none> 443/TCP 26m
redis-master ClusterIP 10.15.252.226 <none> 6379/TCP 40s
Our cluster now looks like this:
Our Redis master is a single pod. We can make Redis worker replicas to make it highly available.
In the same way that the Redis master pod deployment is configured through a yaml file, so are the Redis workers:
# redis-slave-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis-slave
spec:
# Create 2 pods
# If this deployment is created, and there are no pods, 2 will be spun up on the cluster
# If at any point there are more than 2 pods with this configuration on the cluster,
# pods will be killed until 2 are reached
replicas: 2
template:
metadata:
# services matching these labels in the selector config will forward traffic
# to pods created using this config
labels:
app: redis
role: slave
tier: backend
spec:
containers:
- name: slave
image: gcr.io/google_samples/gb-redisslave:v1
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
# If your cluster config does not include a dns service, then to
# instead access an environment variable to find the master
# service's host, comment out the 'value: dns' line above, and
# uncomment the line below:
# value: env
ports:
- containerPort: 6379
To create the Redis worker deployment:
$ kubectl create -f redis-slave-deployment.yaml
We can verify that in addition to our Redis master pod we now have 2 additional Redis slave pods:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-master-7bd4d6ccfd-rh768 1/1 Running 0 22m
redis-slave-84845b8fd8-nz5bb 1/1 Running 0 43s
redis-slave-84845b8fd8-t5jw4 1/1 Running 0 43s
Before data can be read from the Redis slaves, we need a service to route traffic to them. Services provide load balancing to sets of pods.
apiVersion: v1
# this is a service
kind: Service
metadata:
# with the name of redis-slave
name: redis-slave
labels:
app: redis
role: slave
tier: backend
spec:
ports:
- port: 6379
# route traffic to pods deployed with the following labels
selector:
app: redis
role: slave
tier: backend
As with the Redis master service, we run this service in a similar manner:
$ kubectl create -f redis-slave-service.yaml
And we can view the currently running services:
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.15.240.1 <none> 443/TCP 38m
redis-master ClusterIP 10.15.252.226 <none> 6379/TCP 13m
redis-slave ClusterIP 10.15.241.254 <none> 6379/TCP 40s
Our cluster now looks as follows:
With storage up and running, we can now deploy the guestbook servers. As with the Redis workers we will deploy a replicated application using a deployment.
# frontend-deployment.yaml
apiVersion: extensions/v1beta1
# this is a deployment
kind: Deployment
metadata:
# named frontend
name: frontend
spec:
# running on 3 pods
replicas: 3
template:
metadata:
# A service matching the following selectors will route and load balance
# traffic to however many pods we have configured using this deployment
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google-samples/gb-frontend:v4
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
# If your cluster config does not include a dns service, then to
# instead access environment variables to find service host
# info, comment out the 'value: dns' line above, and uncomment the
# line below:
# value: env
ports:
- containerPort: 80
We can create the deployment as with the Redis master and slave deployments:
$ kubectl create -f frontend-deployment.yaml
and verify that our 3 replicas are indeed running.
# select pods to query by a list of labels that identify the frontend
$ kubectl get pods -l app=guestbook -l tier=frontend
The Redis master and slave services are available only within the container cluster. This is because the default type for a service is ClusterIP
. ClusterIP
creates a single IP address for all the pods the service points to, and is only accessible within the cluster.
The frontend needs to be exernally available - i.e. outside of the cluster. Exposing an IP is a billable service. To do this, a service needs to be specified with type: LoadBalancer
.
# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# This line was uncommented to allow for the service to be
# available outside of the cluster
# When the service is created, K8s Engine creates a load balancer
# and external IP.
type: LoadBalancer
# Route traffic incoming on port 80 to port 80 inside the cluster.
# targetPort is not explicitly defined, which results in requests
# being routed to the same port that incoming traffic is configured
# to, i.e. requests on 80 externally will be routed to 80 internally
ports:
- port: 80
# route requests against this service to pods with the following labels
selector:
app: guestbook
tier: frontend
Create the frontend service:
$ kubectl create -f frontend-service.yaml
We can see the newly running service as before:
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend ClusterIP 10.15.246.17 <pending> 80/TCP 1m
kubernetes ClusterIP 10.15.240.1 <none> 443/TCP 1h
redis-master ClusterIP 10.15.252.226 <none> 6379/TCP 36m
redis-slave ClusterIP 10.15.241.254 <none> 6379/TCP 23m
Our cluster now looks like this:
Using kubectl
we can get the external IP of the frontend service (once the IP is configured):
$ kubectl get service frontend
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend LoadBalancer 10.15.241.20 35.193.51.166 80:32323/TCP 1m
Should the frontend suddenly experience a lot of traffic and you need more servers we can scale our service up via kubectl
:
# scale the frontend deployment to 5 replicas
$ kubectl scale deployment frontend --replicas=5
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
frontend-685d7ff496-4rxqk 1/1 Running 0 3s
frontend-685d7ff496-6p2vd 1/1 Running 0 28m
frontend-685d7ff496-6wh8r 1/1 Running 0 3s
frontend-685d7ff496-767g7 1/1 Running 0 28m
frontend-685d7ff496-pvkcb 1/1 Running 0 28m
redis-master-7bd4d6ccfd-rh768 1/1 Running 0 1h
redis-slave-84845b8fd8-nz5bb 1/1 Running 0 41m
redis-slave-84845b8fd8-t5jw4 1/1 Running 0 41m
Deployments can be scaled down in the same way.
Make sure to clean up resources to avoid unnecessary billing:
Deallocate the Cloud load balancer for the frontend:
$ kubectl delete service frontend
This deletes the load balancer asynchronously.
$ gcloud compute forwarding-rules list
Delete the resources associated with the cluster; compute instances, disks, and network resources:
$ gcloud container clusters delete guestbook