diff --git a/prepare-vms/infra/scaleway b/prepare-vms/infra/scaleway index 53f232e7..a39d7633 100644 --- a/prepare-vms/infra/scaleway +++ b/prepare-vms/infra/scaleway @@ -1 +1,3 @@ INFRACLASS=scaleway +#SCW_INSTANCE_TYPE=DEV1-L +#SCW_ZONE=fr-par-2 diff --git a/prepare-vms/lib/infra/scaleway.sh b/prepare-vms/lib/infra/scaleway.sh index 5311b7ec..71227118 100644 --- a/prepare-vms/lib/infra/scaleway.sh +++ b/prepare-vms/lib/infra/scaleway.sh @@ -5,6 +5,9 @@ if ! [ -f ~/.config/scw/config.yaml ]; then warn "~/.config/scw/config.yaml not found." fi +SCW_INSTANCE_TYPE=${SCW_INSTANCE_TYPE-DEV1-M} +SCW_ZONE=${SCW_ZONE-fr-par-1} + infra_list() { scw instance server list -o json | jq -r '.[] | [.id, .name, .state, .commercial_type] | @tsv' @@ -13,9 +16,6 @@ infra_list() { infra_start() { COUNT=$1 - SCW_INSTANCE_TYPE=${SCW_INSTANCE_TYPE-DEV1-M} - SCW_ZONE=${SCW_ZONE-fr-par-1} - for I in $(seq 1 $COUNT); do NAME=$(printf "%s-%03d" $TAG $I) sep "Starting instance $I/$COUNT" @@ -36,16 +36,16 @@ infra_stop() { scw_get_ids_by_tag $TAG | wc -l info "Deleting instances..." scw_get_ids_by_tag $TAG | - xargs -n1 -P10 -I@@ \ - scw instance server delete force-shutdown=true server-id=@@ + xargs -n1 -P10 \ + scw instance server delete zone=${SCW_ZONE} force-shutdown=true with-ip=true } scw_get_ids_by_tag() { TAG=$1 - scw instance server list name=$TAG -o json | jq -r .[].id + scw instance server list zone=${SCW_ZONE} name=$TAG -o json | jq -r .[].id } scw_get_ips_by_tag() { TAG=$1 - scw instance server list name=$TAG -o json | jq -r .[].public_ip.address + scw instance server list zone=${SCW_ZONE} name=$TAG -o json | jq -r .[].public_ip.address } diff --git a/prepare-vms/map-dns.py b/prepare-vms/map-dns.py index 95fee748..1d25eee9 100755 --- a/prepare-vms/map-dns.py +++ b/prepare-vms/map-dns.py @@ -2,11 +2,11 @@ """ There are two ways to use this script: -1. Pass a tag name as a single argument. -It will then take the clusters corresponding to that tag, and assign one -domain name per cluster. Currently it gets the domains from a hard-coded -path. There should be more domains than clusters. -Example: ./map-dns.py 2020-08-15-jp +1. Pass a file name and a tag name as a single argument. +It will load a list of domains from the given file (one per line), +and assign them to the clusters corresponding to that tag. +There should be more domains than clusters. +Example: ./map-dns.py domains.txt 2020-08-15-jp 2. Pass a domain as the 1st argument, and IP addresses then. It will configure the domain with the listed IP addresses. @@ -19,55 +19,53 @@ import requests import sys import yaml -# configurable stuff -domains_file = "../../plentydomains/domains.txt" +# This can be tweaked if necessary. config_file = os.path.join( - os.environ["HOME"], ".config/gandi/config.yaml") -tag = None + os.environ["HOME"], ".config/gandi/config.yaml") apiurl = "https://dns.api.gandi.net/api/v5/domains" - -if len(sys.argv) == 2: - tag = sys.argv[1] - domains = open(domains_file).read().split() - domains = [ d for d in domains if not d.startswith('#') ] - ips = open(f"tags/{tag}/ips.txt").read().split() - settings_file = f"tags/{tag}/settings.yaml" - clustersize = yaml.safe_load(open(settings_file))["clustersize"] -else: - domains = [sys.argv[1]] - ips = sys.argv[2:] - clustersize = len(ips) - -# inferred stuff apikey = yaml.safe_load(open(config_file))["apirest"]["key"] -# now do the fucking work -while domains and ips: - domain = domains[0] - domains = domains[1:] - cluster = ips[:clustersize] - ips = ips[clustersize:] - print(f"{domain} => {cluster}") - zone = "" - node = 0 - for ip in cluster: - node += 1 - zone += f"@ 300 IN A {ip}\n" - zone += f"* 300 IN A {ip}\n" - zone += f"node{node} 300 IN A {ip}\n" - r = requests.put( - f"{apiurl}/{domain}/records", - headers={"x-api-key": apikey}, - data=zone) - print(r.text) +# Figure out if we're called for a bunch of domains, or just one. +first_arg = sys.argv[1] +if os.path.isfile(first_arg): + domains = open(first_arg).read().split() + domains = [ d for d in domains if not d.startswith('#') ] + tag = sys.argv[2] + ips = open(f"tags/{tag}/ips.txt").read().split() + settings_file = f"tags/{tag}/settings.yaml" + clustersize = yaml.safe_load(open(settings_file))["clustersize"] +else: + domains = [first_arg] + ips = sys.argv[2:] + clustersize = len(ips) - #r = requests.get( - # f"{apiurl}/{domain}/records", - # headers={"x-api-key": apikey}, - # ) +# Now, do the work. +while domains and ips: + domain = domains[0] + domains = domains[1:] + cluster = ips[:clustersize] + ips = ips[clustersize:] + print(f"{domain} => {cluster}") + zone = "" + node = 0 + for ip in cluster: + node += 1 + zone += f"@ 300 IN A {ip}\n" + zone += f"* 300 IN A {ip}\n" + zone += f"node{node} 300 IN A {ip}\n" + r = requests.put( + f"{apiurl}/{domain}/records", + headers={"x-api-key": apikey}, + data=zone) + print(r.text) + + #r = requests.get( + # f"{apiurl}/{domain}/records", + # headers={"x-api-key": apikey}, + # ) if domains: - print(f"Good, we have {len(domains)} domains left.") + print(f"Good, we have {len(domains)} domains left.") if ips: - print(f"Crap, we have {len(ips)} IP addresses left.") + print(f"Crap, we have {len(ips)} IP addresses left.") diff --git a/slides/k8s/aggregation-layer.md b/slides/k8s/aggregation-layer.md index 5efd7b1a..82df51c6 100644 --- a/slides/k8s/aggregation-layer.md +++ b/slides/k8s/aggregation-layer.md @@ -58,27 +58,24 @@ *probably aggregation layer* - --- ## How are resources organized? - Let's have a look at the Kubernetes API hierarchical structure -- Useful: `.metadata.selfLink` contains the URI of a resource +- We'll ask `kubectl` to show us the exacts requests that it's making .exercise[ -- Check the `apiVersion` and URI of a "core" resource, e.g. a Node: +- Check the URI for a cluster-scope, "core" resource, e.g. a Node: ```bash - kubectl get nodes -o json | jq .items[0].apiVersion - kubectl get nodes -o json | jq .items[0].metadata.selfLink + kubectl -v6 get node node1 ``` -- Get the `apiVersion` and URI for a "non-core" resource, e.g. a ClusterRole: +- Check the URI for a cluster-scope, "non-core" resource, e.g. a ClusterRole: ```bash - kubectl get clusterrole view -o json | jq .apiVersion - kubectl get clusterrole view -o json | jq .metadata.selfLink + kubectl -v6 get clusterrole view ``` ] @@ -123,6 +120,17 @@ class: extra-details ## Namespaced resources +- What about namespaced resources? + +.exercise[ + +- Check the URI for a namespaced, "core" resource, e.g. a Service: + ```bash + kubectl -v6 get service kubernetes --namespace default + ``` + +] + - Here are what namespaced resources URIs look like: ``` @@ -168,7 +176,7 @@ class: extra-details kubectl get pods --namespace=kube-system --selector=k8s-app=kube-proxy PODNAME=$( kubectl get pods --namespace=kube-system --selector=k8s-app=kube-proxy \ - -o json | jq .items[0].metadata.name) + -o json | jq -r .items[0].metadata.name) ``` - Execute a command in a pod, showing the API requests: diff --git a/slides/k8s/configuration.md b/slides/k8s/configuration.md index c37238d2..1effd876 100644 --- a/slides/k8s/configuration.md +++ b/slides/k8s/configuration.md @@ -60,21 +60,41 @@ ## Command-line arguments -- Pass options to `args` array in the container specification +- Indicate what should run in the container -- Example ([source](https://github.com/coreos/pods/blob/master/kubernetes.yaml#L29)): +- Pass `command` and/or `args` in the container options in a Pod's template + +- Both `command` and `args` are arrays + +- Example ([source](https://github.com/jpetazzo/container.training/blob/main/k8s/consul-1.yaml#L70)): ```yaml - args: - - "--data-dir=/var/lib/etcd" - - "--advertise-client-urls=http://127.0.0.1:2379" - - "--listen-client-urls=http://127.0.0.1:2379" - - "--listen-peer-urls=http://127.0.0.1:2380" - - "--name=etcd" + args: + - "agent" + - "-bootstrap-expect=3" + - "-retry-join=provider=k8s label_selector=\"app=consul\" namespace=\"$(NS)\"" + - "-client=0.0.0.0" + - "-data-dir=/consul/data" + - "-server" + - "-ui" ``` -- The options can be passed directly to the program that we run ... +--- - ... or to a wrapper script that will use them to e.g. generate a config file +## `args` or `command`? + +- Use `command` to override the `ENTRYPOINT` defined in the image + +- Use `args` to keep the `ENTRYPOINT` defined in the image + + (the parameters specified in `args` are added to the `ENTRYPOINT`) + +- In doubt, use `command` + +- It is also possible to use *both* `command` and `args` + + (they will be strung together, just like `ENTRYPOINT` and `CMD`) + +- See the [docs](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes) to see how they interact together --- diff --git a/slides/k8s/exercise-helm.md b/slides/k8s/exercise-helm.md index b41e429f..c4f790e1 100644 --- a/slides/k8s/exercise-helm.md +++ b/slides/k8s/exercise-helm.md @@ -10,7 +10,7 @@ Level 2: make it so that the number of replicas can be set with `--set replicas= Level 3: change the colors of the lego bricks. -(For level 3, fork the repository and use ctr.run to build images.) +(For level 3, you'll have to build/push your own images.) See next slide if you need hints! @@ -44,20 +44,12 @@ Also add `replicas: 5` to `values.yaml` to provide a default value. ## Changing the color -- Fork the repository +- Create an account on e.g. Docker Hub (e.g. `janedoe`) -- Make sure that your fork has valid Dockerfiles - - (or identify a branch that has valid Dockerfiles) - -- Use the following images: - - ctr.run/yourgithubusername/wordsmith/db:branchname - - (replace db with web and words for the other components) +- Create an image repository (e.g. `janedoe/web`) - Change the images and/or CSS in `web/static` -- Commit, push, trigger a rolling update +- Build and push - (`imagePullPolicy` should be `Always`, which is the default) +- Trigger a rolling update using the image you just pushed diff --git a/slides/k8s/helm-intro.md b/slides/k8s/helm-intro.md index 2db93989..6edd0b62 100644 --- a/slides/k8s/helm-intro.md +++ b/slides/k8s/helm-intro.md @@ -244,7 +244,7 @@ fine for personal and development clusters.) - Add the `stable` repo: ```bash - helm repo add stable https://kubernetes-charts.storage.googleapis.com/ + helm repo add stable https://charts.helm.sh/stable ``` ] @@ -255,6 +255,22 @@ It's OK to add a repo that already exists (it will merely update it). --- +class: extra-details + +## Deprecation warning + +- That "stable" is being deprecated, in favor of a more decentralized approach + + (each community / company / group / project hosting their own repository) + +- We're going to use it here for educational purposes + +- But if you're looking for production-grade charts, look elsewhere! + + (namely, on the Helm Hub) + +--- + ## Search available charts - We can search available charts with `helm search` diff --git a/slides/k8s/hpa-v2.md b/slides/k8s/hpa-v2.md index 32155a17..35dec98d 100644 --- a/slides/k8s/hpa-v2.md +++ b/slides/k8s/hpa-v2.md @@ -72,7 +72,7 @@ - Deploy DockerCoins, and scale up the `worker` Deployment: ```bash - kubectl apply -f ~/container.training/k8/dockercoins.yaml + kubectl apply -f ~/container.training/k8s/dockercoins.yaml kubectl scale deployment worker --replicas=10 ``` @@ -118,7 +118,7 @@ - Deploy `httplat`: ```bash - kubectl create deployment httplat -- httplat http://rng/ + kubectl create deployment httplat --image=jpetazzo/httplat -- httplat http://rng/ ``` - Expose it: @@ -512,20 +512,20 @@ no custom metrics API (custom.metrics.k8s.io) registered Here is the rule that we need to add to the configuration: ```yaml -- seriesQuery: | - httplat_latency_seconds_sum{kubernetes_namespace!="",kubernetes_name!=""} - resources: - overrides: - kubernetes_namespace: - resource: namespace - kubernetes_name: - resource: service - name: - matches: "httplat_latency_seconds_sum" - as: "httplat_latency_seconds" - metricsQuery: | - rate(httplat_latency_seconds_sum{<<.LabelMatchers>>}[2m]) - /rate(httplat_latency_seconds_count{<<.LabelMatchers>>}[2m]) + - seriesQuery: | + httplat_latency_seconds_sum{kubernetes_namespace!="",kubernetes_name!=""} + resources: + overrides: + kubernetes_namespace: + resource: namespace + kubernetes_name: + resource: service + name: + matches: "httplat_latency_seconds_sum" + as: "httplat_latency_seconds" + metricsQuery: | + rate(httplat_latency_seconds_sum{<<.LabelMatchers>>}[2m]) + /rate(httplat_latency_seconds_count{<<.LabelMatchers>>}[2m]) ``` (I built it following the [walkthrough](https://github.com/DirectXMan12/k8s-prometheus-adapter/blob/master/docs/config-walkthrough.md @@ -636,7 +636,7 @@ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 Check that our `httplat` metrics are available: ```bash kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1\ -/namespaces/coins/services/httplat/httplat_latency_seconds +/namespaces/customscaling/services/httplat/httplat_latency_seconds ``` Also check the logs of the `prometheus-adapter` and the `kube-controller-manager`. diff --git a/slides/k8s/secrets.md b/slides/k8s/secrets.md index 53112061..772b1395 100644 --- a/slides/k8s/secrets.md +++ b/slides/k8s/secrets.md @@ -68,6 +68,128 @@ “Ah yes, this secret is a ...” +--- + +## Accessing private repositories + +- Let's see how to access an image on private registry! + +- These images are protected by a username + password + + (on some registries, it's token + password, but it's the same thing) + +- To access a private image, we need to: + + - create a secret + + - reference that secret in a Pod template + + - or reference that secret in a ServiceAccount used by a Pod + +--- + +## In practice + +- Let's try to access an image on a private registry! + + - image = docker-registry.enix.io/jpetazzo/private:latest + - user = reader + - password = VmQvqdtXFwXfyy4Jb5DR + +.exercise[ + +- Create a Deployment using that image: + ```bash + kubectl create deployment priv \ + --image=docker-registry.enix.io/jpetazzo/private + ``` + +- Check that the Pod won't start: + ```bash + kubectl get pods --selector=app=priv + ``` + +] + +--- + +## Creating a secret + +- Let's create a secret with the information provided earlier + +.exercise[ + +- Create the registry secret: + ```bash + kubectl create secret docker-registry enix \ + --docker-server=docker-registry.enix.io \ + --docker-username=reader \ + --docker-password=VmQvqdtXFwXfyy4Jb5DR + ``` + +] + +Why do we have to specify the registry address? + +If we use multiple sets of credentials for different registries, it prevents leaking the credentials of one registry to *another* registry. + +--- + +## Using the secret + +- The first way to use a secret is to add it to `imagePullSecrets` + + (in the `spec` section of a Pod template) + +.exercise[ + +- Patch the `priv` Deployment that we created earlier: + ```bash + kubectl patch deploy priv --patch=' + spec: + template: + spec: + imagePullSecrets: + - name: enix + ' + ``` + +] + +--- + +## Checking the results + +.exercise[ + +- Confirm that our Pod can now start correctly: + ```bash + kubectl get pods --selector=app=priv + ``` + +] + +--- + +## Another way to use the secret + +- We can add the secret to the ServiceAccount + +- This is convenient to automatically use credentials for *all* pods + + (as long as they're using a specific ServiceAccount, of course) + +.exercise[ + +- Add the secret to the ServiceAccount: + ```bash + kubectl patch serviceaccount default --patch=' + imagePullSecrets: + - name: enix + ' + ``` + +] --- diff --git a/slides/k8s/shippingimages.md b/slides/k8s/shippingimages.md index 37abef9d..5c35d016 100644 --- a/slides/k8s/shippingimages.md +++ b/slides/k8s/shippingimages.md @@ -94,28 +94,20 @@ ## Building on the fly -- Some services can build images on the fly from a repository +- Conceptually, it is possible to build images on the fly from a repository - Example: [ctr.run](https://ctr.run/) -.exercise[ + (deprecated in August 2020, after being aquired by Datadog) -- Use ctr.run to automatically build a container image and run it: +- It did allow something like this: ```bash docker run ctr.run/github.com/jpetazzo/container.training/dockercoins/hasher ``` - +- No alternative yet -] - -There might be a long pause before the first layer is pulled, -because the API behind `docker pull` doesn't allow to stream build logs, and there is no feedback during the build. - -It is possible to view the build logs by setting up an account on [ctr.run](https://ctr.run/). + (free startup idea, anyone?) ???