diff --git a/docs/kube.yml b/docs/kube.yml index bee4b5db..29c9e648 100644 --- a/docs/kube.yml +++ b/docs/kube.yml @@ -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) ]] - - diff --git a/docs/kubectlexpose.md b/docs/kubectlexpose.md new file mode 100644 index 00000000..30dd00dc --- /dev/null +++ b/docs/kubectlexpose.md @@ -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 +
(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. diff --git a/docs/kubectlrun.md b/docs/kubectlrun.md new file mode 100644 index 00000000..3cb50c3f --- /dev/null +++ b/docs/kubectlrun.md @@ -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 +
+are we getting 1000 packets per second +
+of ICMP ECHO traffic from EC2 ?!? +] +] diff --git a/docs/kubectlscale.md b/docs/kubectlscale.md new file mode 100644 index 00000000..b8789ca3 --- /dev/null +++ b/docs/kubectlscale.md @@ -0,0 +1,2 @@ +# Scaling a deployment + diff --git a/docs/ourapponkube.md b/docs/ourapponkube.md new file mode 100644 index 00000000..c30648af --- /dev/null +++ b/docs/ourapponkube.md @@ -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 +
(make sure you specify tag `:2` to run the new version!) + +- It will store images and layers to the local filesystem +
(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, +
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!* \ No newline at end of file diff --git a/docs/setup-k8s.md b/docs/setup-k8s.md new file mode 100644 index 00000000..86178f82 --- /dev/null +++ b/docs/setup-k8s.md @@ -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) +
+ (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 +
+ (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