diff --git a/dockercoins/Tiltfile b/dockercoins/Tiltfile new file mode 100644 index 00000000..d06b8120 --- /dev/null +++ b/dockercoins/Tiltfile @@ -0,0 +1,49 @@ +k8s_yaml(blob(''' +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: registry + name: registry +spec: + selector: + matchLabels: + app: registry + template: + metadata: + labels: + app: registry + spec: + containers: + - image: registry + name: registry +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: registry + name: registry +spec: + ports: + - port: 5000 + protocol: TCP + targetPort: 5000 + nodePort: 30555 + selector: + app: registry + type: NodePort +''')) +default_registry('localhost:30555') +docker_build('dockercoins/hasher', 'hasher') +docker_build('dockercoins/rng', 'rng') +docker_build('dockercoins/webui', 'webui') +docker_build('dockercoins/worker', 'worker') +k8s_yaml('../k8s/dockercoins.yaml') + +# Uncomment the following line to let tilt run with the default kubeadm cluster-admin context. +#allow_k8s_contexts('kubernetes-admin@kubernetes') + +# While we're here: if you're controlling a remote cluster, uncomment that line. +# It will create a port forward so that you can access the remote registry. +#k8s_resource(workload='registry', port_forwards='30555:5000') 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/commands.sh b/prepare-vms/lib/commands.sh index cd965e11..822a9539 100644 --- a/prepare-vms/lib/commands.sh +++ b/prepare-vms/lib/commands.sh @@ -69,11 +69,14 @@ _cmd_deploy() { echo deploying > tags/$TAG/status sep "Deploying tag $TAG" - # Wait for cloudinit to be done + # If this VM image is using cloud-init, + # wait for cloud-init to be done pssh " - while [ ! -f /var/lib/cloud/instance/boot-finished ]; do - sleep 1 - done" + if [ -d /var/lib/cloud ]; then + while [ ! -f /var/lib/cloud/instance/boot-finished ]; do + sleep 1 + done + fi" # Special case for scaleway since it doesn't come with sudo if [ "$INFRACLASS" = "scaleway" ]; then @@ -102,6 +105,12 @@ _cmd_deploy() { sudo apt-get update && sudo apt-get install -y python-yaml" + # If there is no "python" binary, symlink to python3 + #pssh " + #if ! which python; then + # ln -s $(which python3) /usr/local/bin/python + #fi" + # Copy postprep.py to the remote machines, and execute it, feeding it the list of IP addresses pssh -I tee /tmp/postprep.py >/tmp/pp.out 2>>/tmp/pp.err" /tmp/token && @@ -721,7 +737,7 @@ _cmd_helmprom() { need_tag pssh " if i_am_first_node; then - sudo -u docker -H helm helm repo add prometheus-community https://prometheus-community.github.io/helm-charts/ + sudo -u docker -H helm repo add prometheus-community https://prometheus-community.github.io/helm-charts/ sudo -u docker -H helm install prometheus prometheus-community/prometheus \ --namespace kube-system \ --set server.service.type=NodePort \ diff --git a/prepare-vms/lib/infra/linode.sh b/prepare-vms/lib/infra/linode.sh new file mode 100644 index 00000000..8a7630d3 --- /dev/null +++ b/prepare-vms/lib/infra/linode.sh @@ -0,0 +1,58 @@ +if ! command -v linode-cli >/dev/null; then + warn "Linode CLI (linode-cli) not found." +fi +if ! [ -f ~/.config/linode-cli ]; then + warn "~/.config/linode-cli not found." +fi + +# To view available regions: "linode-cli regions list" +LINODE_REGION=${LINODE_REGION-us-west} + +# To view available types: "linode-cli linodes types" +LINODE_TYPE=${LINODE_TYPE-g6-standard-2} + +infra_list() { + linode-cli linodes list --json | + jq -r '.[] | [.id, .label, .status, .type] | @tsv' +} + +infra_start() { + COUNT=$1 + + for I in $(seq 1 $COUNT); do + NAME=$(printf "%s-%03d" $TAG $I) + sep "Starting instance $I/$COUNT" + info " Zone: $LINODE_REGION" + info " Name: $NAME" + info " Instance type: $LINODE_TYPE" + ROOT_PASS="$(base64 /dev/urandom | cut -c1-20 | head -n 1)" + linode-cli linodes create \ + --type=${LINODE_TYPE} --region=${LINODE_REGION} \ + --image=linode/ubuntu18.04 \ + --authorized_keys="${LINODE_SSHKEY}" \ + --root_pass="${ROOT_PASS}" \ + --tags=${TAG} --label=${NAME} + done + sep + + linode_get_ips_by_tag $TAG > tags/$TAG/ips.txt +} + +infra_stop() { + info "Counting instances..." + linode_get_ids_by_tag $TAG | wc -l + info "Deleting instances..." + linode_get_ids_by_tag $TAG | + xargs -n1 -P10 \ + linode-cli linodes delete +} + +linode_get_ids_by_tag() { + TAG=$1 + linode-cli linodes list --tags $TAG --json | jq -r ".[].id" +} + +linode_get_ips_by_tag() { + TAG=$1 + linode-cli linodes list --tags $TAG --json | jq -r ".[].ipv4[0]" +} 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/lib/pssh.sh b/prepare-vms/lib/pssh.sh index ca3bc639..fb855696 100644 --- a/prepare-vms/lib/pssh.sh +++ b/prepare-vms/lib/pssh.sh @@ -18,11 +18,11 @@ pssh() { echo "[parallel-ssh] $@" export PSSH=$(which pssh || which parallel-ssh) - if [ "$INFRACLASS" = hetzner ]; then - LOGIN=root - else - LOGIN=ubuntu - fi + case "$INFRACLASS" in + hetzner) LOGIN=root ;; + linode) LOGIN=root ;; + *) LOGIN=ubuntu ;; + esac $PSSH -h $HOSTFILE -l $LOGIN \ --par 100 \ diff --git a/prepare-vms/map-dns.py b/prepare-vms/map-dns.py index 95fee748..58f54e1c 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. +domain_or_domain_file = sys.argv[1] +if os.path.isfile(domain_or_domain_file): + domains = open(domain_or_domain_file).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 = [domain_or_domain_file] + 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/0.yml b/slides/0.yml new file mode 100644 index 00000000..36bd3a0e --- /dev/null +++ b/slides/0.yml @@ -0,0 +1,2 @@ +content: +- k8s/netpol.md diff --git a/slides/_redirects b/slides/_redirects index d708d6aa..8a7f788d 100644 --- a/slides/_redirects +++ b/slides/_redirects @@ -2,6 +2,7 @@ #/ /kube-halfday.yml.html 200! #/ /kube-fullday.yml.html 200! #/ /kube-twodays.yml.html 200! +/ kube.yml.html 200! # And this allows to do "git clone https://container.training". /info/refs service=git-upload-pack https://github.com/jpetazzo/container.training/info/refs?service=git-upload-pack diff --git a/slides/autopilot/package-lock.json b/slides/autopilot/package-lock.json index a709ddbe..1d9fade4 100644 --- a/slides/autopilot/package-lock.json +++ b/slides/autopilot/package-lock.json @@ -24,14 +24,9 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "arraybuffer.slice": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", - "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, "backo2": { "version": "1.0.2", @@ -39,27 +34,19 @@ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" }, "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" }, "body-parser": { "version": "1.18.2", @@ -83,20 +70,15 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "component-inherit": { "version": "0.0.3", @@ -152,58 +134,76 @@ "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" }, "engine.io": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz", - "integrity": "sha1-PQIRtwpVLOhB/8fahiezAamkFi4=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", + "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", "requires": { - "accepts": "1.3.3", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "2.6.9", - "engine.io-parser": "2.1.1", - "uws": "0.14.5", - "ws": "3.3.3" + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "~7.4.2" }, "dependencies": { - "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "mime-types": "2.1.17", - "negotiator": "0.6.1" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, "engine.io-client": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.4.tgz", - "integrity": "sha1-T88TcLRxY70s6b4nM5ckMDUNTqE=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz", + "integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==", "requires": { - "component-emitter": "1.2.1", + "component-emitter": "~1.3.0", "component-inherit": "0.0.3", - "debug": "2.6.9", - "engine.io-parser": "2.1.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.2.0", "has-cors": "1.1.0", "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "3.3.3", - "xmlhttprequest-ssl": "1.5.4", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } } }, "engine.io-parser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.1.tgz", - "integrity": "sha1-4Ps/DgRi9/WLt3waUun1p+JuRmg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", + "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", "requires": { "after": "0.8.2", - "arraybuffer.slice": "0.0.6", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary2": "1.0.2" + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.4", + "blob": "0.0.5", + "has-binary2": "~1.0.2" } }, "escape-html": { @@ -278,9 +278,9 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "has-binary2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", - "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "requires": { "isarray": "2.0.1" } @@ -376,11 +376,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -390,20 +385,14 @@ } }, "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "1.0.2" - } + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" }, "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "1.0.2" - } + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" }, "parseurl": { "version": "1.3.2", @@ -487,51 +476,104 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "socket.io": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz", - "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.0.tgz", + "integrity": "sha512-9UPJ1UTvKayuQfVv2IQ3k7tCQC/fboDyIK62i99dAQIyHKaBsNdTpwHLgKJ6guRWxRtC9H+138UwpaGuQO9uWQ==", "requires": { - "debug": "2.6.9", - "engine.io": "3.1.4", - "socket.io-adapter": "1.1.1", - "socket.io-client": "2.0.4", - "socket.io-parser": "3.1.2" + "debug": "~4.1.0", + "engine.io": "~3.5.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.4.0", + "socket.io-parser": "~3.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } }, "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" }, "socket.io-client": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", - "integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", + "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", "requires": { "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "2.6.9", - "engine.io-client": "3.1.4", - "has-cors": "1.1.0", + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "engine.io-client": "~3.5.0", + "has-binary2": "~1.0.2", "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "3.1.2", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "socket.io-parser": "~3.3.0", "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "socket.io-parser": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", + "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", + "requires": { + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "isarray": "2.0.1" + } + } } }, "socket.io-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", - "integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", "requires": { "component-emitter": "1.2.1", - "debug": "2.6.9", - "has-binary2": "1.0.2", + "debug": "~4.1.0", "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } }, "statuses": { @@ -553,11 +595,6 @@ "mime-types": "2.1.17" } }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -568,31 +605,20 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, - "uws": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz", - "integrity": "sha1-Z6rzPEaypYel9mZtAPdpEyjxSdw=", - "optional": true - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" - } + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xmlhttprequest-ssl": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz", - "integrity": "sha1-BPVgkVcks4kIhxXMDteBPpZ3v1c=" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" }, "yeast": { "version": "0.1.2", diff --git a/slides/autopilot/package.json b/slides/autopilot/package.json index b7832ea2..34df3d90 100644 --- a/slides/autopilot/package.json +++ b/slides/autopilot/package.json @@ -3,6 +3,6 @@ "version": "0.0.1", "dependencies": { "express": "^4.16.2", - "socket.io": "^2.0.4" + "socket.io": "^2.4.0" } } diff --git a/slides/images/k8s-net-0-overview.svg b/slides/images/k8s-net-0-overview.svg new file mode 100644 index 00000000..1227c09d --- /dev/null +++ b/slides/images/k8s-net-0-overview.svg @@ -0,0 +1,1060 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slides/images/k8s-net-1-pod-to-pod.svg b/slides/images/k8s-net-1-pod-to-pod.svg new file mode 100644 index 00000000..5358b23d --- /dev/null +++ b/slides/images/k8s-net-1-pod-to-pod.svg @@ -0,0 +1,519 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slides/images/k8s-net-2-pod-to-svc.svg b/slides/images/k8s-net-2-pod-to-svc.svg new file mode 100644 index 00000000..2a2dc80d --- /dev/null +++ b/slides/images/k8s-net-2-pod-to-svc.svg @@ -0,0 +1,587 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slides/images/k8s-net-3-netpol.svg b/slides/images/k8s-net-3-netpol.svg new file mode 100644 index 00000000..af655baf --- /dev/null +++ b/slides/images/k8s-net-3-netpol.svg @@ -0,0 +1,493 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slides/images/k8s-net-4-overview.svg b/slides/images/k8s-net-4-overview.svg new file mode 100644 index 00000000..d1ed10a5 --- /dev/null +++ b/slides/images/k8s-net-4-overview.svg @@ -0,0 +1,1108 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/slides/k8s/accessinternal.md b/slides/k8s/accessinternal.md index a647c38a..d70c882d 100644 --- a/slides/k8s/accessinternal.md +++ b/slides/k8s/accessinternal.md @@ -134,3 +134,17 @@ installed and set up `kubectl` to communicate with your cluster. :EN:- Securely accessing internal services :FR:- Accès sécurisé aux services internes + +:T: Accessing internal services from our local machine + +:Q: What's the advantage of "kubectl port-forward" compared to a NodePort? +:A: It can forward arbitrary protocols +:A: It doesn't require Kubernetes API credentials +:A: It offers deterministic load balancing (instead of random) +:A: ✔️It doesn't expose the service to the public + +:Q: What's the security concept behind "kubectl port-forward"? +:A: ✔️We authenticate with the Kubernetes API, and it forwards connections on our behalf +:A: It detects our source IP address, and only allows connections coming from it +:A: It uses end-to-end mTLS (mutual TLS) to authenticate our connections +:A: There is no security (as long as it's running, anyone can connect from anywhere) 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/dryrun.md b/slides/k8s/authoring-yaml.md similarity index 72% rename from slides/k8s/dryrun.md rename to slides/k8s/authoring-yaml.md index bdeb019d..dbb99f9a 100644 --- a/slides/k8s/dryrun.md +++ b/slides/k8s/authoring-yaml.md @@ -1,6 +1,6 @@ # Authoring YAML -- There are various ways to generate YAML with Kubernetes, e.g.: +- We have already generated YAML implicitly, with e.g.: - `kubectl run` @@ -32,26 +32,63 @@ --- -## We don't have to start from scratch +## Various ways to write YAML -- Create a resource (e.g. Deployment) +- Completely from scratch with our favorite editor -- Dump its YAML with `kubectl get -o yaml ...` + (yeah, right) -- Edit the YAML +- Dump an existing resource with `kubectl get -o yaml ...` -- Use `kubectl apply -f ...` with the YAML file to: + (it is recommended to clean up the result) - - update the resource (if it's the same kind) +- Ask `kubectl` to generate the YAML - - create a new resource (if it's a different kind) + (with a `kubectl create --dry-run -o yaml`) -- Or: Use The Docs, Luke +- Use The Docs, Luke (the documentation almost always has YAML examples) --- +## Generating YAML from scratch + +- Start with a namespace: + ```yaml + kind: Namespace + apiVersion: v1 + metadata: + name: hello + ``` + +- We can use `kubectl explain` to see resource definitions: + ```bash + kubectl explain -r pod.spec + ``` + +- Not the easiest option! + +--- + +## Dump the YAML for an existing resource + +- `kubectl get -o yaml` works! + +- A lot of fields in `metadata` are not necessary + + (`managedFields`, `resourceVersion`, `uid`, `creationTimestamp` ...) + +- Most objects will have a `status` field that is not necessary + +- Default or empty values can also be removed for clarity + +- This can be done manually or with the `kubectl-neat` plugin + + `kubectl get -o yaml ... | kubectl neat` + +--- + ## Generating YAML without creating resources - We can use the `--dry-run` option @@ -63,14 +100,18 @@ kubectl create deployment web --image nginx --dry-run ``` +- Optionally clean it up with `kubectl neat`, too + ] -- We can clean up that YAML even more if we want +Note: in recent versions of Kubernetes, we should use `--dry-run=client` - (for instance, we can remove the `creationTimestamp` and empty dicts) +(Or `--dry-run=server`; more on that later!) --- +class: extra-details + ## Using `--dry-run` with `kubectl apply` - The `--dry-run` option can also be used with `kubectl apply` @@ -87,6 +128,8 @@ --- +class: extra-details + ## The limits of `kubectl apply --dry-run` .exercise[ @@ -112,6 +155,8 @@ The resulting YAML doesn't represent a valid DaemonSet. --- +class: extra-details + ## Server-side dry run - Since Kubernetes 1.13, we can use [server-side dry run and diffs](https://kubernetes.io/blog/2019/01/14/apiserver-dry-run-and-kubectl-diff/) @@ -135,6 +180,8 @@ Instead, it has the fields expected in a DaemonSet. --- +class: extra-details + ## Advantages of server-side dry run - The YAML is verified much more extensively @@ -149,6 +196,8 @@ Instead, it has the fields expected in a DaemonSet. --- +class: extra-details + ## `kubectl diff` - Kubernetes 1.13 also introduced `kubectl diff` @@ -209,3 +258,8 @@ Note: we don't need to specify `--validate=false` here. - check that it still works! - That YAML will be useful later when using e.g. Kustomize or Helm + +??? + +:EN:- Techniques to write YAML manifests +:FR:- Comment écrire des *manifests* YAML diff --git a/slides/k8s/cert-manager.md b/slides/k8s/cert-manager.md index dee9193f..b95949b3 100644 --- a/slides/k8s/cert-manager.md +++ b/slides/k8s/cert-manager.md @@ -242,3 +242,5 @@ class: extra-details :EN:- Obtaining certificates with cert-manager :FR:- Obtenir des certificats avec cert-manager + +:T: Obtaining TLS certificates with cert-manager diff --git a/slides/k8s/cluster-backup.md b/slides/k8s/cluster-backup.md index 113bd134..db8edd92 100644 --- a/slides/k8s/cluster-backup.md +++ b/slides/k8s/cluster-backup.md @@ -338,9 +338,9 @@ docker run --rm --net host -v $PWD:/vol \ (e.g. [Portworx](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-snapshots/) can [create snapshots through annotations](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-snapshots/snaps-annotations/#taking-periodic-snapshots-on-a-running-pod)) -- Option 3: [snapshots through Kubernetes API](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/) +- Option 3: [snapshots through Kubernetes API](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) - (now in alpha for a few storage providers: GCE, OpenSDS, Ceph, Portworx) + (Generally available since Kuberentes 1.20 for a number of [CSI](https://kubernetes.io/blog/2019/01/15/container-storage-interface-ga/) volume plugins : GCE, OpenSDS, Ceph, Portworx, etc) --- 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/control-plane-auth.md b/slides/k8s/control-plane-auth.md index aee2f651..ec95e612 100644 --- a/slides/k8s/control-plane-auth.md +++ b/slides/k8s/control-plane-auth.md @@ -92,6 +92,29 @@ --- +## etcd authorization + +- etcd supports RBAC, but Kubernetes doesn't use it by default + + (note: etcd RBAC is completely different from Kubernetes RBAC!) + +- By default, etcd access is "all or nothing" + + (if you have a valid certificate, you get in) + +- Be very careful if you use the same root CA for etcd and other things + + (if etcd trusts the root CA, then anyone with a valid cert gets full etcd access) + +- For more details, check the following resources: + + - [etcd documentation on authentication](https://etcd.io/docs/current/op-guide/authentication/) + + - [PKI The Wrong Way](https://www.youtube.com/watch?v=gcOLDEzsVHI) at KubeCon NA 2020 + +--- + + ## API server clients - The API server has a sophisticated authentication and authorization system 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/gitlab.md b/slides/k8s/gitlab.md new file mode 100644 index 00000000..8f198b52 --- /dev/null +++ b/slides/k8s/gitlab.md @@ -0,0 +1,447 @@ +# CI/CD with GitLab + +- In this section, we will see how to set up a CI/CD pipeline with GitLab + + (using a "self-hosted" GitLab; i.e. running on our Kubernetes cluster) + +- The big picture: + + - each time we push code to GitLab, it will be deployed in a staging environment + + - each time we push the `production` tag, it will be deployed in production + +--- + +## Disclaimers + +- We'll use GitLab here as an exemple, but there are many other options + + (e.g. some combination of Argo, Harbor, Tekton ...) + +- There are also hosted options + + (e.g. GitHub Actions and many others) + +- We'll use a specific pipeline and workflow, but it's purely arbitrary + + (treat it as a source of inspiration, not a model to be copied!) + +--- + +## Workflow overview + +- Push code to GitLab's git server + +- GitLab notices the `.gitlab-ci.yml` file, which defines our pipeline + +- Our pipeline can have multiple *stages* executed sequentially + + (e.g. lint, build, test, deploy ...) + +- Each stage can have multiple *jobs* executed in parallel + + (e.g. build images in parallel) + +- Each job will be executed in an independent *runner* pod + +--- + +## Pipeline overview + +- Our repository holds source code, Dockerfiles, and a Helm chart + +- *Lint* stage will check the Helm chart validity + +- *Build* stage will build container images + + (and push them to GitLab's integrated registry) + +- *Deploy* stage will deploy the Helm chart, using these images + +- Pushes to `production` will deploy to "the" production namespace + +- Pushes to other tags/branches will deploy to a namespace created on the fly + +- We will discuss shortcomings and alternatives and the end of this chapter! + +--- + +## Lots of requirements + +- We need *a lot* of components to pull this off: + + - a domain name + + - a storage class + + - a TLS-capable ingress controller + + - the cert-manager operator + + - GitLab itself + + - the GitLab pipeline + +- Wow, why?!? + +--- + +## I find your lack of TLS disturbing + +- We need a container registry (obviously!) + +- Docker (and other container engines) *require* TLS on the registry + + (with valid certificates) + +- A few options: + + - use a "real" TLS certificate (e.g. obtained with Let's Encrypt) + + - use a self-signed TLS certificate + + - communicate with the registry over localhost (TLS isn't required then) + +--- + +class: extra-details + +## Why not self-signed certs? + +- When using self-signed certs, we need to either: + + - add the cert (or CA) to trusted certs + + - disable cert validation + +- This needs to be done on *every client* connecting to the registry: + + - CI/CD pipeline (building and pushing images) + + - container engine (deploying the images) + + - other tools (e.g. container security scanner) + +- It's doable, but it's a lot of hacks (especially when adding more tools!) + +--- + +class: extra-details + +## Why not localhost? + +- TLS is usually not required when the registry is on localhost + +- We could expose the registry e.g. on a `NodePort` + +- ... And then tweak the CI/CD pipeline to use that instead + +- This is great when obtaining valid certs is difficult: + + - air-gapped or internal environments (that can't use Let's Encrypt) + + - no domain name available + +- Downside: the registry isn't easily or safely available from outside + + (the `NodePort` essentially defeats TLS) + +--- + +class: extra-details + +## Can we use `nip.io`? + +- We will use Let's Encrypt + +- Let's Encrypt has a quota of certificates per domain + + (in 2020, that was [50 certificates per week per domain](https://letsencrypt.org/docs/rate-limits/)) + +- So if we all use `nip.io`, we will probably run into that limit + +- But you can try and see if it works! + +--- + +## Ingress + +- We will assume that we have a domain name pointing to our cluster + + (i.e. with a wildcard record pointing to at least one node of the cluster) + +- We will get traffic in the cluster by leveraging `ExternalIPs` services + + (but it would be easy to use `LoadBalancer` services instead) + +- We will use Traefik as the ingress controller + + (but any other one should work too) + +- We will use cert-manager to obtain certificates with Let's Encrypt + +--- + +## Other details + +- We will deploy GitLab with its official Helm chart + +- It will still require a bunch of parameters and customization + +- We also need a Storage Class + + (unless our cluster already has one, of course) + +- We suggest the [Rancher local path provisioner](https://github.com/rancher/local-path-provisioner) + +--- + +## Setting everything up + +1. `git clone https://github.com/jpetazzo/kubecoin` + +2. `export EMAIL=xxx@example.com DOMAIN=awesome-kube-ci.io` + + (we need a real email address and a domain pointing to the cluster!) + +3. `. setup-gitlab-on-k8s.rc` + + (this doesn't do anything, but defines a number of helper functions) + +4. Execute each helper function, one after another + + (try `do_[TAB]` to see these functions) + +--- + +## Local Storage + +`do_1_localstorage` + +Applies the YAML directly from Rancher's repository. + +Annotate the Storage Class so that it becomes the default one. + +--- + +## Traefik + +`do_2_traefik_with_externalips` + +Install the official Traefik Helm chart. + +Instead of a `LoadBalancer` service, use a `ClusterIP` with `ExternalIPs`. + +Automatically infer the `ExternalIPs` from `kubectl get nodes`. + +Enable TLS. + +--- + +## cert-manager + +`do_3_certmanager` + +Install cert-manager using their official YAML. + +Easy-peasy. + +--- + +## Certificate issuers + +`do_4_issuers` + +Create a couple of `ClusterIssuer` resources for cert-manager. + +(One for the staging Let's Encrypt environment, one for production.) + +Note: this requires to specify a valid `$EMAIL` address! + +Note: if this fails, wait a bit and try again (cert-manager needs to be up). + +--- + +## GitLab + +`do_5_gitlab` + +Deploy GitLab using their official Helm chart. + +We pass a lot of parameters to this chart: +- the domain name to use +- disable GitLab's own ingress and cert-manager +- annotate the ingress resources so that cert-manager kicks in +- bind the shell service (git over SSH) to port 222 to avoid conflict +- use ExternalIPs for that shell service + +Note: on modest cloud instances, it can take 10 minutes for GitLab to come up. + +We can check the status with `kubectl get pods --namespace=gitlab` + +--- + +## Log into GitLab and configure it + +`do_6_showlogin` + +This will get the GitLab root password (stored in a Secret). + +Then we need to: +- log into GitLab +- add our SSH key (top-right user menu → settings, then SSH keys on the left) +- create a project (using the + menu next to the search bar on top) +- go to project configuration (on the left, settings → CI/CD) +- add a `KUBECONFIG` file variable with the content of our `.kube/config` file +- go to settings → access tokens to create a read-only registry token +- add variables `REGISTRY_USER` and `REGISTRY_PASSWORD` with that token +- push our repo (`git remote add gitlab ...` then `git push gitlab ...`) + +--- + +## Monitoring progress and troubleshooting + +- Click on "CI/CD" in the left bar to view pipelines + +- If you see a permission issue mentioning `system:serviceaccount:gitlab:...`: + + *make sure you did set `KUBECONFIG` correctly!* + +- GitLab will create namespaces named `gl--` + +- At the end of the deployment, the web UI will be available on some unique URL + + (`http://---gitlab.`) + +--- + +## Production + +- `git tag -f production && git push -f --tags` + +- Our CI/CD pipeline will deploy on the production URL + + (`http://--gitlab.`) + +- It will do it *only* if that same git commit was pushed to staging first + + (look in the pipeline configuration file to see how it's done!) + +--- + +## Let's talk about build + +- There are many ways to build container images on Kubernetes + +- ~~And they all suck~~ Many of them have inconveniencing issues + +- Let's do a quick review! + +--- + +## Docker-based approaches + +- Bind-mount the Docker socket + + - very easy, but requires Docker Engine + - build resource usage "evades" Kubernetes scheduler + - insecure + +- Docker-in-Docker in a pod + + - requires privileged pod + - insecure + - approaches like rootless or sysbox might help in the future + +- External build host + + - more secure + - requires resources outside of the Kubernetes cluster + +--- + +## Non-privileged builders + +- Kaniko + + - each build runs in its own containers or pod + - no caching by default + - registry-based caching is possible + +- BuildKit / `docker buildx` + + - can leverage Docker Engine or long-running Kubernetes worker pod + - supports distributed, multi-arch build farms + - basic caching out of the box + - can also leverage registry-based caching + +--- + +## Other approaches + +- Ditch the Dockerfile! + +- bazel + +- jib + +- ko + +- etc. + +--- + +## Discussion + +- Our CI/CD workflow is just *one* of the many possibilities + +- It would be nice to add some actual unit or e2e tests + +- Map the production namespace to a "real" domain name + +- Automatically remove older staging environments + + (see e.g. [kube-janitor](https://codeberg.org/hjacobs/kube-janitor)) + +- Deploy production to a separate cluster + +- Better segregate permissions + + (don't give `cluster-admin` to the GitLab pipeline) + +--- + +## Pros + +- GitLab is an amazing, open source, all-in-one platform + +- Available as hosted, community, or enterprise editions + +- Rich ecosystem, very customizable + +- Can run on Kubernetes, or somewhere else + +--- + +## Cons + +- It can be difficult to use components separately + + (e.g. use a different registry, or a different job runner) + +- More than one way to configure it + + (it's not an opinionated platform) + +- Not "Kubernetes-native" + + (for instance, jobs are not Kubernetes jobs) + +- Job latency could be improved + +*Note: most of these drawbacks are the flip side of the "pros" on the previous slide!* + +??? + +:EN:- CI/CD with GitLab +:FR:- CI/CD avec GitLab diff --git a/slides/k8s/helm-intro.md b/slides/k8s/helm-intro.md index 2db93989..66385ab8 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` @@ -446,3 +462,17 @@ All unspecified values will take the default values defined in the chart. :FR:- Fonctionnement général de Helm :FR:- Installer des composants via Helm :FR:- Helm 2, Helm 3, et le *Helm Hub* + +:T: Getting started with Helm and its concepts + +:Q: Which comparison is the most adequate? +:A: Helm is a firewall, charts are access lists +:A: ✔️Helm is a package manager, charts are packages +:A: Helm is an artefact repository, charts are artefacts +:A: Helm is a CI/CD platform, charts are CI/CD pipelines + +:Q: What's required to distribute a Helm chart? +:A: A Helm commercial license +:A: A Docker registry +:A: An account on the Helm Hub +:A: ✔️An HTTP server 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/k9s.md b/slides/k8s/k9s.md new file mode 100644 index 00000000..9c5174e4 --- /dev/null +++ b/slides/k8s/k9s.md @@ -0,0 +1,141 @@ +# k9s + +- Somewhere in between CLI and GUI (or web UI), we can find the magic land of TUI + + - [Text-based user interfaces](https://en.wikipedia.org/wiki/Text-based_user_interface) + + - often using libraries like [curses](https://en.wikipedia.org/wiki/Curses_%28programming_library%29) and its successors + +- Some folks love them, some folks hate them, some are indifferent ... + +- But it's nice to have different options! + +- Let's see one particular TUI for Kubernetes: [k9s](https://k9scli.io/) + +--- + +## Installing k9s + +- If you are using a training cluster or the [shpod](https://github.com/jpetazzo/shpod) image, k9s is pre-installed + +- Otherwise, it can be installed easily: + + - with [various package managers](https://k9scli.io/topics/install/) + + - or by fetching a [binary release](https://github.com/derailed/k9s/releases) + +- We don't need to set up or configure anything + + (it will use the same configuration as `kubectl` and other well-behaved clients) + +- Just run `k9s` to fire it up! + +--- + +## What kind to we want to see? + +- Press `:` to change the type of resource to view + +- Then type, for instance, `ns` or `namespace` or `nam[TAB]`, then `[ENTER]` + +- Use the arrows to move down to e.g. `kube-system`, and press `[ENTER]` + +- Or, type `/kub` or `/sys` to filter the output, and press `[ENTER]` twice + + (once to exit the filter, once to enter the namespace) + +- We now see the pods in `kube-system`! + +--- + +## Interacting with pods + +- `l` to view logs + +- `d` to describe + +- `s` to get a shell (won't work if `sh` isn't available in the container image) + +- `e` to edit + +- `shift-f` to define port forwarding + +- `ctrl-k` to kill + +- `[ESC]` to get out or get back + +--- + +## Quick navigation between namespaces + +- On top of the screen, we should see shortcuts like this: + ``` + <0> all + <1> kube-system + <2> default + ``` + +- Pressing the corresponding number switches to that namespace + + (or shows resources across all namespaces with `0`) + +- Locate a namespace with a copy of DockerCoins, and go there! + +--- + +## Interacting with Deployments + +- View Deployments (type `:` `deploy` `[ENTER]`) + +- Select e.g. `worker` + +- Scale it with `s` + +- View its aggregated logs with `l` + +--- + +## Exit + +- Exit at any time with `Ctrl-C` + +- k9s will "remember" where you were + + (and go back there next time you run it) + +--- + +## Pros + +- Very convenient to navigate through resources + + (hopping from a deployment, to its pod, to another namespace, etc.) + +- Very convenient to quickly view logs of e.g. init containers + +- Very convenient to get a (quasi) realtime view of resources + + (if we use `watch kubectl get` a lot, we will probably like k9s) + +--- + +## Cons + +- Doesn't promote automation / scripting + + (if you repeat the same things over and over, there is a scripting opportunity) + +- Not all features are available + + (e.g. executing arbitrary commands in containers) + +--- + +## Conclusion + +Try it out, and see if it makes you more productive! + +??? + +:EN:- The k9s TUI +:FR:- L'interface texte k9s diff --git a/slides/k8s/kubenet.md b/slides/k8s/kubenet.md index 73c6af0f..abbc18ca 100644 --- a/slides/k8s/kubenet.md +++ b/slides/k8s/kubenet.md @@ -52,7 +52,7 @@ - There are literally dozens of implementations out there - (15 are listed in the Kubernetes documentation) + (https://github.com/containernetworking/cni/ lists more than 25 plugins) - Pods have level 3 (IP) connectivity, but *services* are level 4 (TCP or UDP) @@ -128,6 +128,36 @@ class: extra-details --- +class: pic + +![Overview of the three Kubernetes network layers](images/k8s-net-0-overview.svg) + +--- + +class: pic + +![Pod-to-pod network](images/k8s-net-1-pod-to-pod.svg) + +--- + +class: pic + +![Pod-to-service network](images/k8s-net-2-pod-to-svc.svg) + +--- + +class: pic + +![Network policies](images/k8s-net-3-netpol.svg) + +--- + +class: pic + +![View with all the layers again](images/k8s-net-4-overview.svg) + +--- + class: extra-details ## Even more moving parts diff --git a/slides/k8s/kyverno.md b/slides/k8s/kyverno.md index 83403caa..06cad7b1 100644 --- a/slides/k8s/kyverno.md +++ b/slides/k8s/kyverno.md @@ -321,9 +321,9 @@ class: extra-details - Try to apply a few color labels: ```bash - kubectl label test-color-2 color=purple - kubectl label test-color-2 color=red - kubectl label test-color-2 color=blue --overwrite + kubectl label pod test-color-2 color=purple + kubectl label pod test-color-2 color=red + kubectl label pod test-color-2 color=blue --overwrite ``` ] @@ -432,9 +432,9 @@ class: extra-details - Try to apply a few color labels: ```bash - kubectl label test-color-3 color=purple - kubectl label test-color-3 color=red - kubectl label test-color-3 color- + kubectl label pod test-color-3 color=purple + kubectl label pod test-color-3 color=red + kubectl label pod test-color-3 color- ``` ] diff --git a/slides/k8s/netpol.md b/slides/k8s/netpol.md index f92148cf..0ee5de8c 100644 --- a/slides/k8s/netpol.md +++ b/slides/k8s/netpol.md @@ -427,26 +427,34 @@ troubleshoot easily, without having to poke holes in our firewall. --- -## Further resources +## Tools and resources -- As always, the [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/network-policies/) is a good starting point +- [Cilium Network Policy Editor](https://editor.cilium.io/) -- The API documentation has a lot of detail about the format of various objects: +- [Tufin Network Policy Viewer](https://orca.tufin.io/netpol/) - - [NetworkPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#networkpolicy-v1-networking-k8s-io) - - - [NetworkPolicySpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#networkpolicyspec-v1-networking-k8s-io) - - - [NetworkPolicyIngressRule](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#networkpolicyingressrule-v1-networking-k8s-io) - - - etc. - -- And two resources by [Ahmet Alp Balkan](https://ahmet.im/): +- Two resources by [Ahmet Alp Balkan](https://ahmet.im/): - a [very good talk about network policies](https://www.youtube.com/watch?list=PLj6h78yzYM2P-3-xqvmWaZbbI1sW-ulZb&v=3gGpMmYeEO8) at KubeCon North America 2017 - a repository of [ready-to-use recipes](https://github.com/ahmetb/kubernetes-network-policy-recipes) for network policies +--- + +## Documentation + +- As always, the [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/network-policies/) is a good starting point + +- The API documentation has a lot of detail about the format of various objects: + + - [NetworkPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#networkpolicy-v1-networking-k8s-io) + + - [NetworkPolicySpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#networkpolicyspec-v1-networking-k8s-io) + + - [NetworkPolicyIngressRule](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#networkpolicyingressrule-v1-networking-k8s-io) + + - etc. + ??? :EN:- Isolating workloads with Network Policies diff --git a/slides/k8s/resource-limits.md b/slides/k8s/resource-limits.md index ae1b73dd..75be8b6e 100644 --- a/slides/k8s/resource-limits.md +++ b/slides/k8s/resource-limits.md @@ -40,6 +40,112 @@ --- +class: extra-details + +## CPU limits implementation details + +- A container with a CPU limit will be "rationed" by the kernel + +- Every `cfs_period_us`, it will receive a CPU quota, like an "allowance" + + (that interval defaults to 100ms) + +- Once it has used its quota, it will be stalled until the next period + +- This can easily result in throttling for bursty workloads + + (see details on next slide) + +--- + +class: extra-details + +## A bursty example + +- Web service receives one request per minute + +- Each request takes 1 second of CPU + +- Average load: 0.16% + +- Let's say we set a CPU limit of 10% + +- This means CPU quotas of 10ms every 100ms + +- Obtaining the quota for 1 second of CPU will take 10 seconds + +- Observed latency will be 10 seconds (... actually 9.9s) instead of 1 second + + (real-life scenarios will of course be less extreme, but they do happen!) + +--- + +class: extra-details + +## Multi-core scheduling details + +- Each core gets a small share of the container's CPU quota + + (this avoids locking and contention on the "global" quota for the container) + +- By default, the kernel distributes that quota to CPUs in 5ms increments + + (tunable with `kernel.sched_cfs_bandwidth_slice_us`) + +- If a containerized process (or thread) uses up its local CPU quota: + + *it gets more from the "global" container quota (if there's some left)* + +- If it "yields" (e.g. sleeps for I/O) before using its local CPU quota: + + *the quota is **soon** returned to the "global" container quota, **minus** 1ms* + +--- + +class: extra-details + +## Low quotas on machines with many cores + +- The local CPU quota is not immediately returned to the global quota + + - this reduces locking and contention on the global quota + + - but this can cause starvation when many threads/processes become runnable + +- That 1ms that "stays" on the local CPU quota is often useful + + - if the thread/process becomes runnable, it can be scheduled immediately + + - again, this reduces locking and contention on the global quota + + - but if the thread/process doesn't become runnable, it is wasted! + + - this can become a huge problem on machines with many cores + +--- + +class: extra-details + +## CPU limits in a nutshell + +- Beware if you run small bursty workloads on machines with many cores! + + ("highly-threaded, user-interactive, non-cpu bound applications") + +- Check the `nr_throttled` and `throttled_time` metrics in `cpu.stat` + +- Possible solutions/workarounds: + + - be generous with the limits + + - make sure your kernel has the [appropriate patch](https://lkml.org/lkml/2019/5/17/581) + + - use [static CPU manager policy](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy) + +For more details, check [this blog post](https://erickhun.com/posts/kubernetes-faster-services-no-cpu-limits/) or these ones ([part 1](https://engineering.indeedblog.com/blog/2019/12/unthrottled-fixing-cpu-limits-in-the-cloud/), [part 2](https://engineering.indeedblog.com/blog/2019/12/cpu-throttling-regression-fix/)). + +--- + ## Exceeding memory limits - Memory needs to be swapped out before being reclaimed 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/setup-devel.md b/slides/k8s/setup-devel.md index b15e77c1..5cecbd22 100644 --- a/slides/k8s/setup-devel.md +++ b/slides/k8s/setup-devel.md @@ -63,6 +63,7 @@ ```bash k3d cluster create groscluster \ --image rancher/k3s:v1.18.9-k3s1 --servers 3 --agents 5 + ``` (3 nodes for the control plane + 5 worker nodes) 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?) ??? diff --git a/slides/k8s/tilt.md b/slides/k8s/tilt.md new file mode 100644 index 00000000..678f005a --- /dev/null +++ b/slides/k8s/tilt.md @@ -0,0 +1,302 @@ +# Tilt + +- What does a development workflow look like? + + - make changes + + - test / see these changes + + - repeat! + +- What does it look like, with containers? + + 🤔 + +--- + +## Basic Docker workflow + +- Preparation + + - write Dockerfiles + +- Iteration + + - edit code + - `docker build` + - `docker run` + - test + - `docker stop` + +Straightforward when we have a single container. + +--- + +## Docker workflow with volumes + +- Preparation + + - write Dockerfiles + - `docker build` + `docker run` + +- Iteration + + - edit code + - test + +Note: only works with interpreted languages. +
+(Compiled languages require extra work.) + +--- + +## Docker workflow with Compose + +- Preparation + + - write Dockerfiles + Compose file + - `docker-compose up` + +- Iteration + + - edit code + - test + - `docker-compose up` (as needed) + +Simplifies complex scenarios (multiple containers). +
+Facilitates updating images. + +--- + +## Basic Kubernetes workflow + +- Preparation + + - write Dockerfiles + - write Kubernetes YAML + - set up container registry + +- Iteration + + - edit code + - build images + - push images + - update Kubernetes resources + +Seems simple enough, right? + +--- + +## Basic Kubernetes workflow + +- Preparation + + - write Dockerfiles + - write Kubernetes YAML + - **set up container registry** + +- Iteration + + - edit code + - build images + - **push images** + - update Kubernetes resources + +Ah, right ... + +--- + +## We need a registry + +- Remember "build, ship, and run" + +- Registries are involved in the "ship" phase + +- With Docker, we were building and running on the same node + +- We didn't need a registry! + +- With Kubernetes, though ... + +--- + +## Special case of single node clusters + +- If our Kubernetes has only one node ... + +- ... We can build directly on that node ... + +- ... We don't need to push images ... + +- ... We don't need to run a registry! + +- Examples: Docker Desktop, Minikube ... + +--- + +## When we have more than one node + +- Which registry should we use? + + (Docker Hub, Quay, cloud-based, self-hosted ...) + +- Should we use a single registry, or one per cluster or environment? + +- Which tags and credentials should we use? + + (in particular when using a shared registry!) + +- How do we provision that registry and its users? + +- How do we adjust our Kubernetes YAML manifests? + + (e.g. to inject image names and tags) + +--- + +## More questions + +- The whole cycle (build+push+update) is expensive + +- If we have many services, how do we update only the ones we need? + +- Can we take shortcuts? + + (e.g. synchronized files without going through a whole build+push+update cycle) + +--- + +## Tilt + +- Tilt is a tool to address all these questions + +- There are other similar tools (e.g. Skaffold) + +- We arbitrarily decided to focus on that one + +--- + +## Tilt in practice + +- The `dockercoins` directory in our repository has a `Tiltfile` + +- Go to that directory and try `tilt up` + +- Tilt should refuse to start, but it will explain why + +- Edit the `Tiltfile` accordingly and try again + +- Open the Tilt web UI + + (if running Tilt on a remote machine, you will need `tilt up --host 0.0.0.0`) + +- Watch as the Dockercoins app is built, pushed, started + +--- + +## What's in our Tiltfile? + +- Kubernetes manifests for a local registry + +- Kubernetes manifests for DockerCoins + +- Instructions indicating how to build DockerCoins' images + +- A tiny bit of sugar + + (telling Tilt which registry to use) + +--- + + +## How does it work? + +- Tilt keeps track of dependencies between files and resources + + (a bit like a `make` that would run continuously) + +- It automatically alters some resources + + (for instance, it updates the images used in our Kubernetes manifests) + +- That's it! + +(And of course, it provides a great web UI, lots of libraries, etc.) + +--- + +## What happens when we edit a file (1/2) + +- Let's change e.g. `worker/worker.py` + +- Thanks to this line, + ```python + docker_build('dockercoins/worker', 'worker') + ``` + ... Tilt watches the `worker` directory and uses it to build `dockercoins/worker` + +- Thanks to this line, + ```python + default_registry('localhost:30555') + ``` + ... Tilt actually renames `dockercoins/worker` to `localhost:30555/dockercoins_worker` + +- Tilt will tag the image with something like `tilt-xxxxxxxxxx` + +--- + +## What happens when we edit a file (2/2) + +- Thanks to this line, + ```python + k8s_yaml('../k8s/dockercoins.yaml') + ``` + ... Tilt is aware of our Kubernetes resources + +- The `worker` Deployment uses `dockercoins/worker`, so it must be updated + +- `dockercoins/worker` becomes `localhost:30555/dockercoins_worker:tilt-xxx` + +- The `worker` Deployment gets updated on the Kubernetes cluster + +- All these operations (and their log output) are visible in the Tilt UI + +--- + +## Configuration file format + +- The Tiltfile is written in [Starlark](https://github.com/bazelbuild/starlark) + + (essentially a subset of Python) + +- Tilt monitors the Tiltfile too + + (so it reloads it immediately when we change it) + +--- + +## Tilt "killer features" + +- Dependency engine + + (build or run only what's necessary) + +- Ability to watch resources + + (execute actions immediately, without explicitly running a command) + +- Rich library of function and helpers + + (build container images, manipulate YAML manifests...) + +- Convenient UI (web; TUI also available) + + (provides immediate feedback and logs) + +- Extensibility! + +??? + +:EN:- Development workflow with Tilt +:FR:- Développer avec Tilt diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml index 72c9f631..a3c9ad5d 100644 --- a/slides/kube-fullday.yml +++ b/slides/kube-fullday.yml @@ -58,11 +58,13 @@ content: #- k8s/setup-managed.md #- k8s/setup-selfhosted.md #- k8s/dashboard.md + #- k8s/k9s.md + #- k8s/tilt.md #- k8s/kubectlscale.md - k8s/scalingdockercoins.md - shared/hastyconclusions.md - k8s/daemonset.md - #- k8s/dryrun.md + #- k8s/authoring-yaml.md #- k8s/exercise-yaml.md #- k8s/localkubeconfig.md #- k8s/accessinternal.md @@ -82,6 +84,7 @@ content: #- k8s/helm-create-better-chart.md #- k8s/helm-secrets.md #- k8s/exercise-helm.md + #- k8s/gitlab.md #- k8s/create-chart.md #- k8s/create-more-charts.md #- k8s/netpol.md diff --git a/slides/kube-halfday.yml b/slides/kube-halfday.yml index 0aaea969..42da801c 100644 --- a/slides/kube-halfday.yml +++ b/slides/kube-halfday.yml @@ -58,6 +58,8 @@ content: #- k8s/accessinternal.md #- k8s/kubectlproxy.md - - k8s/dashboard.md + #- k8s/k9s.md + #- k8s/tilt.md #- k8s/kubectlscale.md - k8s/scalingdockercoins.md - shared/hastyconclusions.md diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml index 0fe9be4b..b615c6e5 100644 --- a/slides/kube-selfpaced.yml +++ b/slides/kube-selfpaced.yml @@ -59,11 +59,13 @@ content: - k8s/setup-managed.md - k8s/setup-selfhosted.md - k8s/dashboard.md + - k8s/k9s.md + - k8s/tilt.md #- k8s/kubectlscale.md - k8s/scalingdockercoins.md - shared/hastyconclusions.md - k8s/daemonset.md - - k8s/dryrun.md + - k8s/authoring-yaml.md #- k8s/exercise-yaml.md - - k8s/rollout.md @@ -86,6 +88,7 @@ content: - k8s/helm-create-better-chart.md - k8s/helm-secrets.md #- k8s/exercise-helm.md + - k8s/gitlab.md - - k8s/netpol.md - k8s/authn-authz.md diff --git a/slides/kube-twodays.yml b/slides/kube.yml similarity index 93% rename from slides/kube-twodays.yml rename to slides/kube.yml index 553e11a3..aac65962 100644 --- a/slides/kube-twodays.yml +++ b/slides/kube.yml @@ -2,9 +2,9 @@ title: | Kubernetes Training Flatiron Health -#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)" +chat: "[#techops-k8s-training](https://flatiron.slack.com/archives/C01RQU6JJ07)" #chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)" -chat: "In person!" +#chat: "In person!" gitrepo: github.com/jpetazzo/container.training @@ -20,7 +20,7 @@ content: - logistics.md - k8s/intro.md - shared/about-slides.md -- shared/chat-room-im.md +- shared/chat-room-slack.md #- shared/chat-room-zoom-meeting.md #- shared/chat-room-zoom-webinar.md - shared/toc.md @@ -55,7 +55,7 @@ content: - k8s/scalingdockercoins.md - shared/hastyconclusions.md - k8s/daemonset.md - - k8s/dryrun.md + - k8s/authoring-yaml.md #- k8s/exercise-yaml.md - #MODULE4 - k8s/namespaces.md diff --git a/slides/markmaker.py b/slides/markmaker.py index 5173cd93..7a5a0772 100755 --- a/slides/markmaker.py +++ b/slides/markmaker.py @@ -213,6 +213,7 @@ def processcontent(content, filename): return (content, titles) if os.path.isfile(content): return processcontent(open(content).read(), content) + logging.warning("Content spans only one line (it's probably a file name) but no file found: {}".format(content)) if isinstance(content, list): subparts = [processcontent(c, filename) for c in content] markdown = "\n---\n".join(c[0] for c in subparts)