mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-07-22 06:16:31 +00:00
Add how to run and expose services on kube
This commit is contained in:
@@ -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
138
docs/kubectlexpose.md
Normal 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
197
docs/kubectlrun.md
Normal 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
2
docs/kubectlscale.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Scaling a deployment
|
||||
|
||||
348
docs/ourapponkube.md
Normal file
348
docs/ourapponkube.md
Normal 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
67
docs/setup-k8s.md
Normal 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
|
||||
Reference in New Issue
Block a user