Add how to run and expose services on kube

This commit is contained in:
Jérôme Petazzoni
2017-10-11 23:31:39 +02:00
parent 20e9517722
commit 4eaf2310b6
6 changed files with 756 additions and 23 deletions

View File

@@ -88,24 +88,10 @@ chapters:
- concepts-k8s.md
- kubectlget.md
- setup-k8s.md
- - firstservice.md
- ourapponswarm.md
- updatingservices.md
- healthchecks.md
- - operatingswarm.md
- netshoot.md
- ipsec.md
- swarmtools.md
- - security.md
- secrets.md
- leastprivilege.md
- apiscope.md
- logging.md
- metrics.md
- stateful.md
- extratips.md
- end.md
- - kubectlrun.md
- kubectlexpose.md
- ourapponkube.md
- kubectlscale.md
- |
class: title
@@ -116,8 +102,3 @@ chapters:
Jérôme ([@jpetazzo](https://twitter.com/jpetazzo)) — [@docker](https://twitter.com/docker)
]]
<!--
Tiffany ([@tiffanyfayj](https://twitter.com/tiffanyfayj))
AJ ([@s0ulshake](https://twitter.com/s0ulshake))
-->

138
docs/kubectlexpose.md Normal file
View File

@@ -0,0 +1,138 @@
# Exposing containers
- `kubectl expose` creates a *service* for existing pods
- A *service* is a stable address for a pod (or a bunch of pods)
- If we want to connect to our pod(s), we need to create a *service*
- Once a service is created, `kube-dns` will allow us to resolve it by name
(i.e. after creating service `hello`, the name `hello` will resolve to something)
- There are different types of services, detailed on the following slides:
`ClusterIP`, `NodePort`, `LoadBalancer`, `ExternalName`
---
## Basic service types
- `ClusterIP` (default type)
- a virtual IP address is allocated for the service (in an internal, private range)
- this IP address is reachable only from within the cluster (nodes and pods)
- our code can connect to the service using the original port number
- `NodePort`
- a port is allocated for the service (by default, in the 30000-32768 range)
- that port is made available *on all our nodes* and anybody can connect to it
- our code must be changed to connect to that new port number
These service types are always available.
Under the hood: `kube-proxy` is using a userland proxy and a bunch of `iptables` rules.
---
## More service types
- `LoadBalancer`
- an external load balancer is allocated for the service
- the load balancer is configured accordingly
<br/>(e.g.: a `NodePort` service is created, and the load balancer sends traffic to that port)
- `ExternalName`
- the DNS entry managed by `kube-dns` will just be a `CNAME` to a provided record
- no port, no IP address, no nothing else is allocated
The `LoadBalancer` type is currently only available on AWS, Azure, and GCE.
---
## Running containers with open ports
- Since `ping` doesn't have anything to connect to, we'll have to run something else
.exercise[
- Start a bunch of ElasticSearch containers:
```bash
kubectl run elastic --image=elasticsearch:2 --replicas=7
```
- Watch them being started:
```bash
kubectl get pods -w
```
]
The `-w` option "watches" events happening on the specified resources.
Note: please DO NOT call the service `search`. It would collide with the TLD.
---
## Exposing our deployment
- We'll create a default `ClusterIP` service
.exercise[
- Expose the ElasticSearch HTTP API port:
```bash
kubectl expose deploy/elastic --port 9200
```
- Look up which IP address was allocated:
```bash
kubectl get svc
```
]
---
## Services are layer 4 constructs
- You can assign IP addresses to services, but they are still *layer 4*
(i.e. a service is not an IP address; it's an IP address + protocol + port)
- This is caused by the current implementation of `kube-proxy`
(it relies on mechanisms that don't support layer 3)
- As a result: you *have to* indicate the port number for your service
- Running services with arbitrary port (or port ranges) requires hacks
(e.g. host networking mode)
---
## Testing our service
- We will now send a few HTTP requests to our ElasticSearch pods
.exercise[
- Let's obtain the IP address that was allocated for our service, *programatically:*
```bash
IP=$(kubectl get svc elastic -o go-template --template '{{ .spec.clusterIP }}')
```
- Send a few requests:
```bash
curl http://$IP:9200/
```
]
--
Our requests are load balanced across multiple pods.

197
docs/kubectlrun.md Normal file
View File

@@ -0,0 +1,197 @@
# Running our first containers on Kubernetes
- First things first: we cannot run a container
--
- We are going to run a pod, and in that pod there will be a single container
--
- In that container in the pod, we are going to run a simple `ping` command
- Then we are going to start additional copies of the pod
---
## Starting a simple pod with `kubectl run`
- We need to specify at least a *name* and the image we want to use
.exercise[
- Let's ping `goo.gl`:
```bash
kubectl run pingpong --image alpine ping goo.gl
```
]
--
OK, what did just happen?
---
## Behind the scenes of `kubectl run`
- Let's look at the resources that were created by `kubectl run`
.exercise[
- List most resource types:
```bash
kubectl get all
```
]
--
We should see the following things:
- `deploy/pingpong` (the *deployment* that we just created)
- `rs/pingpong-xxxx` (a *replica set* created by the deployment)
- `po/pingpong-yyyy` (a *pod* created by the replica set)
---
## Deployments, replica sets, and replication controllers
- A *deployment* is a high-level construct
- allows scaling, rolling updates, rollbacks
- multiple deployments can be used together to implement a
[canary deployment](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
- delegates pods management to *replica sets*
- A *replica set* is a low-level construct
- makes sure that a given number of identical pods are running
- allows scaling
- rarely used directly
- A *replication controller* is the (deprecated) predecessor of a replica set
---
## Our `pingpong` deployment
- `kubectl run` created a *deployment*, `deploy/pingpong`
- That deployment created a *replica set*, `rs/pingpong-xxxx`
- That replica set created a *pod*, `po/pingpong-yyyy`
- We'll see later how these folks play together for:
- scaling
- high availability
- rolling updates
---
## Viewing container output
- Let's use the `kubectl logs` command
- We will pass either a *pod name*, or a *type/name*
(E.g. if we specify a deployment or replica set, it will get the first pod in it)
- Unless specified otherwise, it will only show logs of the first container in the pod
(Good thing there's only one in ours!)
.exercise[
- View the result of our `ping` command:
```bash
kubectl logs deploy/pingpong
```
]
---
## Streaming logs in real time
- Just like `docker logs`, `kubectl logs` supports convenient options:
- `-f`/`--follow` to stream logs in real time (à la `tail -f`)
- `--tail` to indicate how many lines you want to see (from the end)
- `--since` to get logs only after a given timestamp
.exercise[
- View the latest logs of our `ping` command:
```bash
kubectl logs deploy/pingpong --tail 1 --follow
```
]
---
## Scaling our application
- We can create additional copies of our container (I mean, our pod) with `kubectl scale`
.exercise[
- Scale our `pingpong` deployment:
```bash
kubectl scale deploy/pingpong --replicas 8
```
]
Note: what if we tried to scale `rs/pingpong-xxxx`?
We could! But the *deployment* would notice it right away, and scale back to the initial level.
---
## Viewing logs of multiple pods
- When we specify a deployment name, only one single pod's logs are shown
- We can view the logs of multiple pods by specifying a *selector*
- A selector is a logic expression using *labels*
- Conveniently, when you `kubectl run somename`, the associated objects have a `run=somename` label
.exercise[
- View the last line of log from all pods with the `run=pingpong` label:
```bash
kubectl logs -l run=pingpong --tail 1
```
]
Unfortunately, `--follow` cannot (yet) be used to stream the logs from multiple containers.
---
class: title
.small[
Meanwhile, at the Google NOC ...
.small[
Why the hell
<br/>
are we getting 1000 packets per second
<br/>
of ICMP ECHO traffic from EC2 ?!?
]
]

2
docs/kubectlscale.md Normal file
View File

@@ -0,0 +1,2 @@
# Scaling a deployment

348
docs/ourapponkube.md Normal file
View File

@@ -0,0 +1,348 @@
class: title
Our app on Kube
---
## What's on the menu?
In this part, we will:
- **build** images for our app,
- **ship** these images with a registry,
- **run** deployments using these images,
- expose these deployments so they can communicate with each other,
- expose the web UI so we can access it from outside.
---
## The plan
- Build on our control node (`node1`)
- Tag images so that they are named `$REGISTRY/servicename`
- Upload them to a registry
- Create deployments using the images
- Expose (with a ClusterIP) the services that need to communicate
- Expose (with a NodePort) the WebUI
---
## Which registry do we want to use?
- We could use the Docker Hub
- Or a service offered by our cloud provider (GCR, ECR...)
- Or we could just self-host that registry
*We'll self-host the registry because it's the most generic solution for this workshop.*
---
## Using the open source registry
- We need to run a `registry:2` container
<br/>(make sure you specify tag `:2` to run the new version!)
- It will store images and layers to the local filesystem
<br/>(but you can add a config file to use S3, Swift, etc.)
- Docker *requires* TLS when communicating with the registry
- unless for registries on `127.0.0.0/8` (i.e. `localhost`)
- or with the Engine flag `--insecure-registry`
- Our strategy: publish the registry container on a NodePort,
<br/>so that it's available through `127.0.0.1:xxxxx` on each node
---
# Deploying a self-hosted registry
- We will deploy a registry container, and expose it with a NodePort
.exercise[
- Create the registry service:
```bash
kubectl deploy registry --image=registry:2
```
- Expose it on a NodePort:
```bash
kubectl expose deploy/registry --port=5000 --type=NodePort
```
]
---
## Connecting to our registry
- We need to find out which port has been allocated
.exercise[
- View the service details:
```bash
kubectl describe svc/registry
```
- Get the port number programmatically:
```bash
NODEPORT=$(kubectl get svc/registry -o json | jq .spec.ports[0].nodePort)
REGISTRY=127.0.0.1:$NODEPORT
```
]
---
## Testing our registry
- A convenient Docker registry API route to remember is `/v2/_catalog`
.exercise[
- View the repositories currently held in our registry:
```bash
curl $REGISTRY/v2/_catalog
```
]
--
We should see:
```json
{"repositories":[]}
```
---
## Testing our local registry
- We can retag a small image, and push it to the registry
.exercise[
- Make sure we have the busybox image, and retag it:
```bash
docker pull busybox
docker tag busybox $REGISTRY/busybox
```
- Push it:
```bash
docker push $REGISTRY/busybox
```
]
---
## Checking again what's on our local registry
- Let's use the same endpoint as before
.exercise[
- Ensure that our busybox image is now in the local registry:
```bash
curl $REGISTRY/v2/_catalog
```
]
The curl command should now output:
```json
{"repositories":["busybox"]}
```
---
## Building and pushing our images
- We are going to use a convenient feature of Docker Compose
.exercise[
- Go to the `stacks` directory:
```bash
cd ~/orchestration-workshop/stacks
```
- Build and push the images:
```bash
docker-compose -f dockercoins.yml build
docker-compose -f dockercoins.yml push
```
]
Let's have a look at the `dockercoins.yml` file while this is building and pushing.
---
```yaml
version: "3"
services:
rng:
build: dockercoins/rng
image: ${REGISTRY-127.0.0.1:5000}/rng:${TAG-latest}
deploy:
mode: global
...
redis:
image: redis
...
worker:
build: dockercoins/worker
image: ${REGISTRY-127.0.0.1:5000}/worker:${TAG-latest}
...
deploy:
replicas: 10
```
.warning[Just in case you were wondering ... Docker "services" are not Kubernetes "services".]
---
## Deploying all the things
- We can now deploy our code (as well as a redis instance)
.exercise[
- Deploy `redis`:
```bash
kubectl deploy redis --image=redis
```
- Deploy everything else:
```bash
for SERVICE in hasher rng webui worker; do
kubectl deploy $SERVICE --image=$REGISTRY/$SERVICE
done
```
]
---
## Is this working?
- After waiting for the deployment to complete, let's look at the logs!
(Hint: use `kubectl get deploy -w` to watch deployment events)
.exercise[
- Look at some logs:
```bash
kubectl logs deploy/rng
kubectl logs deploy/worker
```
]
--
🤔 `rng` is fine ... But not `worker`.
--
💡 Oh right! We forgot to `expose`.
---
# Exposing services internally
- Three deployments need to be reachable by others: `hasher`, `redis`, `rng`
- `worker` doesn't need to be exposed
- `webui` will be dealth with later
.exercise[
- Expose each service, specifying the right port:
```bash
kubectl expose redis --port 6379
kubectl expose rng --port 80
kubectl expose hasher --port 80
```
]
---
## Is this working yet?
- The `worker` has an infinite loop, that retries 10 seconds after an error
.exercise[
- Stream the worker's logs:
```bash
kubectl logs deploy/worker --follow
```
(Give it about 10 seconds to recover)
]
--
We should now see the `worker`, well, working happily.
---
# Exposing services for external access
- Now we would like to access the Web UI
- We will expose it with a `NodePort`
(just like we did for the registry)
.exercise[
- Create a `NodePort` service for the Web UI:
```bash
kubectl expose deploy/webui --type=NodePort --port=80
```
- Check the port that was allocated:
```bash
kubectl get svc
```
]
---
## Accessing the web UI
- We can now connect to *any node*, on the allocated node port, to view the web UI
.exercise[
- Open the web UI in your browser (http://node-ip-address:3xxxx/)
]
--
*Alright, we're back to where we started, when we were running on a single node!*

67
docs/setup-k8s.md Normal file
View File

@@ -0,0 +1,67 @@
# Setting up Kubernetes
- How did we setup these Kubernetes clusters that we're using?
--
- We used `kubeadm` on "fresh" EC2 instances with Ubuntu 16.04 LTS
1. Install Docker
2. Install Kubernetes packages
3. Run `kubeadm init` on the master node
4. Setup Weave (the overlay network)
<br/>
(that step is just one `kubectl apply` command; discussed later)
5. Run `kubeadm join` on the other nodes (with the token produced by `kubeadm init`)
6. Copy the configuration file generated by `kubeadm init`
---
## `kubeadm` drawbacks
- Doesn't setup Docker or any other container engine
- Doesn't setup the overlay network
- Scripting is complex
<br/>
(because extracting the token requires advanced `kubectl` commands)
- Doesn't setup multi-master (no high availability)
--
- It's still twice as much steps as setting up a Swarm cluster 😕
---
## Other deployment options
- If you are on Google Cloud:
[GKE](https://cloud.google.com/container-engine/)
Empirically the best Kubernetes deployment out there
- If you are on AWS:
[kops](https://github.com/kubernetes/kops)
... But with AWS re:invent just around the corner, expect some changes
- On a local machine:
[minikube](https://kubernetes.io/docs/getting-started-guides/minikube/)
FIXME
- If you want something customizable:
[kubicorn](https://github.com/kris-nova/kubicorn)
Probably the closest to a multi-cloud/hybrid solution so far, but in development
- Also, many commercial options!
FIXME