Compare commits

..

8 Commits

Author SHA1 Message Date
Jerome Petazzoni
3797fc6f9f woops update chat link 2020-11-30 16:56:26 +01:00
Jerome Petazzoni
242a3d9ddf Update course schedule 2020-11-30 16:31:12 +01:00
Jerome Petazzoni
a14f5e81ce Merge branch 'master' into 2020-11-nr 2020-11-30 00:29:48 +01:00
Jérôme Petazzoni
109f6503e4 Merge pull request #574 from jsubirat/patch-1
Update kyverno.md
2020-11-19 17:27:30 +01:00
Jérôme Petazzoni
337be57182 Merge pull request #575 from jsubirat/patch-2
Update kyverno.md
2020-11-19 17:27:03 +01:00
jsubirat
63d88236b2 Update kyverno.md
Adds missing `pod`s in the commands
2020-11-19 12:14:42 +01:00
jsubirat
799aa21302 Update kyverno.md
Adds missing `pod` in the command
2020-11-19 12:11:17 +01:00
Jerome Petazzoni
95247d6d39 put together NR EUR content 2020-11-16 08:27:39 +01:00
93 changed files with 1173 additions and 27688 deletions

View File

@@ -1,49 +0,0 @@
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')

View File

@@ -62,8 +62,11 @@ spec:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: consul
matchExpressions:
- key: app
operator: In
values:
- consul
topologyKey: kubernetes.io/hostname
terminationGracePeriodSeconds: 10
containers:
@@ -85,4 +88,7 @@ spec:
lifecycle:
preStop:
exec:
command: [ "sh", "-c", "consul leave" ]
command:
- /bin/sh
- -c
- consul leave

View File

@@ -69,8 +69,11 @@ spec:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: consul
matchExpressions:
- key: app
operator: In
values:
- persistentconsul
topologyKey: kubernetes.io/hostname
terminationGracePeriodSeconds: 10
containers:
@@ -95,4 +98,7 @@ spec:
lifecycle:
preStop:
exec:
command: [ "sh", "-c", "consul leave" ]
command:
- /bin/sh
- -c
- consul leave

View File

@@ -1,24 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: openebs-local-hostpath-pod
spec:
volumes:
- name: storage
persistentVolumeClaim:
claimName: local-hostpath-pvc
containers:
- name: better
image: alpine
command:
- sh
- -c
- |
while true; do
echo "$(date) [$(hostname)] Kubernetes is better with PVs." >> /mnt/storage/greet.txt
sleep $(($RANDOM % 5 + 20))
done
volumeMounts:
- mountPath: /mnt/storage
name: storage

View File

@@ -3,6 +3,8 @@ apiVersion: v1
kind: PersistentVolume
metadata:
name: consul-node2
annotations:
node: node2
spec:
capacity:
storage: 10Gi
@@ -24,6 +26,8 @@ apiVersion: v1
kind: PersistentVolume
metadata:
name: consul-node3
annotations:
node: node3
spec:
capacity:
storage: 10Gi
@@ -45,6 +49,8 @@ apiVersion: v1
kind: PersistentVolume
metadata:
name: consul-node4
annotations:
node: node4
spec:
capacity:
storage: 10Gi

View File

@@ -1,13 +0,0 @@
#!/bin/sh
# Create an EKS cluster.
# This is not idempotent (each time you run it, it creates a new cluster).
eksctl create cluster \
--node-type=t3.large \
--nodes-max=10 \
--alb-ingress-access \
--asg-access \
--ssh-access \
--with-oidc \
#

View File

@@ -1,32 +0,0 @@
#!/bin/sh
# For each user listed in "users.txt", create an IAM user.
# Also create AWS API access keys, and store them in "users.keys".
# This is idempotent (you can run it multiple times, it will only
# create the missing users). However, it will not remove users.
# Note that you can remove users from "users.keys" (or even wipe
# that file out entirely) and then this script will delete their
# keys and generate new keys for them (and add the new keys to
# "users.keys".)
echo "Getting list of existing users ..."
aws iam list-users --output json | jq -r .Users[].UserName > users.tmp
for U in $(cat users.txt); do
if ! grep -qw $U users.tmp; then
echo "Creating user $U..."
aws iam create-user --user-name=$U \
--tags=Key=container.training,Value=1
fi
if ! grep -qw $U users.keys; then
echo "Listing keys for user $U..."
KEYS=$(aws iam list-access-keys --user=$U | jq -r .AccessKeyMetadata[].AccessKeyId)
for KEY in $KEYS; do
echo "Deleting key $KEY for user $U..."
aws iam delete-access-key --user=$U --access-key-id=$KEY
done
echo "Creating access key for user $U..."
aws iam create-access-key --user=$U --output json \
| jq -r '.AccessKey | [ .UserName, .AccessKeyId, .SecretAccessKey ] | @tsv' \
>> users.keys
fi
done

View File

@@ -1,51 +0,0 @@
#!/bin/sh
# Create an IAM policy to authorize users to do "aws eks update-kubeconfig".
# This is idempotent, which allows to update the policy document below if
# you want the users to do other things as well.
# Note that each time you run this script, it will actually create a new
# version of the policy, set that version as the default version, and
# remove all non-default versions. (Because you can only have up to
# 5 versions of a given policy, so you need to clean them up.)
# After running that script, you will want to attach the policy to our
# users (check the other scripts in that directory).
POLICY_NAME=user.container.training
POLICY_DOC='{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"eks:DescribeCluster"
],
"Resource": "arn:aws:eks:*",
"Effect": "Allow"
}
]
}'
ACCOUNT=$(aws sts get-caller-identity | jq -r .Account)
aws iam create-policy-version \
--policy-arn arn:aws:iam::$ACCOUNT:policy/$POLICY_NAME \
--policy-document "$POLICY_DOC" \
--set-as-default
# For reference, the command below creates a policy without versioning:
#aws iam create-policy \
#--policy-name user.container.training \
#--policy-document "$JSON"
for VERSION in $(
aws iam list-policy-versions \
--policy-arn arn:aws:iam::$ACCOUNT:policy/$POLICY_NAME \
--query 'Versions[?!IsDefaultVersion].VersionId' \
--output text)
do
aws iam delete-policy-version \
--policy-arn arn:aws:iam::$ACCOUNT:policy/$POLICY_NAME \
--version-id "$VERSION"
done
# For reference, the command below shows all users using the policy:
#aws iam list-entities-for-policy \
#--policy-arn arn:aws:iam::$ACCOUNT:policy/$POLICY_NAME

View File

@@ -1,14 +0,0 @@
#!/bin/sh
# Attach our user policy to all the users defined in "users.txt".
# This should be idempotent, because attaching the same policy
# to the same user multiple times doesn't do anything.
ACCOUNT=$(aws sts get-caller-identity | jq -r .Account)
POLICY_NAME=user.container.training
for U in $(cat users.txt); do
echo "Attaching policy to user $U ..."
aws iam attach-user-policy \
--user-name $U \
--policy-arn arn:aws:iam::$ACCOUNT:policy/$POLICY_NAME
done

View File

@@ -1,24 +0,0 @@
#!/bin/sh
# Update the aws-auth ConfigMap to map our IAM users to Kubernetes users.
# Each user defined in "users.txt" will be mapped to a Kubernetes user
# with the same name, and put in the "container.training" group, too.
# This is idempotent.
# WARNING: this will wipe out the mapUsers component of the aws-auth
# ConfigMap, removing all users that aren't in "users.txt".
# It won't touch mapRoles, so it shouldn't break the role mappings
# put in place by EKS.
ACCOUNT=$(aws sts get-caller-identity | jq -r .Account)
rm -f users.map
for U in $(cat users.txt); do
echo "\
- userarn: arn:aws:iam::$ACCOUNT:user/$U
username: $U
groups: [ container.training ]\
" >> users.map
done
kubectl create --namespace=kube-system configmap aws-auth \
--dry-run=client --from-file=mapUsers=users.map -o yaml \
| kubectl apply -f-

View File

@@ -1,65 +0,0 @@
#!/bin/sh
# Create a shared Kubernetes Namespace ("container-training") as well as
# individual namespaces for every user in "users.txt", and set up a bunch
# of permissions.
# Specifically:
# - each user gets "view" permissions in the "default" Namespace
# - each user gets "edit" permissions in the "container-training" Namespace
# - each user gets permissions to list Nodes and Namespaces
# - each user gets "admin" permissions in their personal Namespace
# Note that since Kubernetes Namespaces can't have dots in their names,
# if a user has dots, dots will be mapped to dashes.
# So user "ada.lovelace" will get namespace "ada-lovelace".
# This is kind of idempotent (but will raise a bunch of errors for objects
# that already exist).
# TODO: if this needs to evolve, replace all the "create" operations by
# "apply" operations. But this is good enough for now.
kubectl create rolebinding --namespace default container.training \
--group=container.training --clusterrole=view
kubectl create clusterrole view-nodes \
--verb=get,list,watch --resource=node
kubectl create clusterrolebinding view-nodes \
--group=container.training --clusterrole=view-nodes
kubectl create clusterrole view-namespaces \
--verb=get,list,watch --resource=namespace
kubectl create clusterrolebinding view-namespaces \
--group=container.training --clusterrole=view-namespaces
kubectl create namespace container-training
kubectl create rolebinding --namespace container-training edit \
--group=container.training --clusterrole=edit
# Note: API calls to EKS tend to be fairly slow. To optimize things a bit,
# instead of running "kubectl" N times, we generate a bunch of YAML and
# apply it. It will still generate a lot of API calls but it's much faster
# than calling "kubectl" N times. It might be possible to make this even
# faster by generating a "kind: List" (I don't know if this would issue
# a single API calls or multiple ones; TBD!)
for U in $(cat users.txt); do
NS=$(echo $U | tr . -)
cat <<EOF
---
kind: Namespace
apiVersion: v1
metadata:
name: $NS
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: admin
namespace: $NS
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: $U
EOF
done | kubectl create -f-

View File

@@ -1,76 +0,0 @@
#!/bin/sh
# Create an IAM role to be used by a Kubernetes ServiceAccount.
# The role isn't given any permissions yet (this has to be done by
# another script in this series), but a properly configured Pod
# should still be able to execute "aws sts get-caller-identity"
# and confirm that it's using that role.
# This requires the cluster to have an attached OIDC provider.
# This should be the case if the cluster has been created with
# the scripts in this directory; otherwise, this can be done with
# the subsequent command, which is idempotent:
# eksctl utils associate-iam-oidc-provider --cluster cluster-name-12341234 --approve
# The policy document used below will authorize all ServiceAccounts
# in the "container-training" Namespace to use that role.
# This script will also annotate the container-training:default
# ServiceAccount so that it can use that role.
# This script is not quite idempotent: if you want to use a new
# trust policy, some work will be required. (You can delete the role,
# but that requires detaching the associated policies. There might also
# be a way to update the trust policy directly; we didn't investigate this
# further at this point.)
if [ "$1" ]; then
CLUSTER="$1"
else
echo "Please indicate cluster to use. Available clusters:"
aws eks list-clusters --output table
exit 1
fi
ACCOUNT=$(aws sts get-caller-identity | jq -r .Account)
OIDC=$(aws eks describe-cluster --name $CLUSTER --query cluster.identity.oidc.issuer --output text | cut -d/ -f3-)
ROLE_NAME=s3-reader-container-training
TRUST_POLICY=$(envsubst <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${ACCOUNT}:oidc-provider/${OIDC}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"${OIDC}:sub": ["system:serviceaccount:container-training:*"]
}
}
}
]
}
EOF
)
aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document "$TRUST_POLICY"
kubectl annotate serviceaccounts \
--namespace container-training default \
"eks.amazonaws.com/role-arn=arn:aws:iam::$ACCOUNT:role/$ROLE_NAME" \
--overwrite
exit
# Here are commands to delete the role:
for POLICY_ARN in $(aws iam list-attached-role-policies --role-name $ROLE_NAME --query 'AttachedPolicies[*].PolicyArn' --output text); do aws iam detach-role-policy --role-name $ROLE_NAME --policy-arn $POLICY_ARN; done
aws iam delete-role --role-name $ROLE_NAME
# Merging the policy with the existing policies:
{
aws iam get-role --role-name s3-reader-container-training | jq -r .Role.AssumeRolePolicyDocument.Statement[]
echo "$TRUST_POLICY" | jq -r .Statement[]
} | jq -s '{"Version": "2012-10-17", "Statement": .}' > /tmp/policy.json
aws iam update-assume-role-policy \
--role-name $ROLE_NAME \
--policy-document file:///tmp/policy.json

View File

@@ -1,54 +0,0 @@
#!/bin/sh
# Create an S3 bucket with two objects in it:
# - public.txt (world-readable)
# - private.txt (private)
# Also create an IAM policy granting read-only access to the bucket
# (and therefore, to the private object).
# Finally, attach the policy to an IAM role (for instance, the role
# created by another script in this directory).
# This isn't idempotent, but it can be made idempotent by replacing the
# "aws iam create-policy" call with "aws iam create-policy-version" and
# a bit of extra elbow grease. (See other scripts in this directory for
# an example).
ACCOUNT=$(aws sts get-caller-identity | jq -r .Account)
BUCKET=container.training
ROLE_NAME=s3-reader-container-training
POLICY_NAME=s3-reader-container-training
POLICY_DOC=$(envsubst <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject*"
],
"Resource": [
"arn:aws:s3:::$BUCKET",
"arn:aws:s3:::$BUCKET/*"
]
}
]
}
EOF
)
aws iam create-policy \
--policy-name $POLICY_NAME \
--policy-doc "$POLICY_DOC"
aws s3 mb s3://container.training
echo "this is a public object" \
| aws s3 cp - s3://container.training/public.txt \
--acl public-read
echo "this is a private object" \
| aws s3 cp - s3://container.training/private.txt \
--acl private
aws iam attach-role-policy \
--role-name "$ROLE_NAME" \
--policy-arn arn:aws:iam::$ACCOUNT:policy/$POLICY_NAME

View File

@@ -1,50 +0,0 @@
ada.lovelace
adele.goldstine
amanda.jones
anita.borg
ann.kiessling
barbara.mcclintock
beatrice.worsley
bessie.blount
betty.holberton
beulah.henry
carleen.hutchins
caroline.herschel
dona.bailey
dorothy.hodgkin
ellen.ochoa
edith.clarke
elisha.collier
elizabeth.feinler
emily.davenport
erna.hoover
frances.spence
gertrude.blanch
grace.hopper
grete.hermann
giuliana.tesoro
harriet.tubman
hedy.lamarr
irma.wyman
jane.goodall
jean.bartik
joy.mangano
josephine.cochrane
katherine.blodgett
kathleen.antonelli
lynn.conway
margaret.hamilton
maria.beasley
marie.curie
marjorie.joyner
marlyn.meltzer
mary.kies
melitta.bentz
milly.koss
radia.perlman
rosalind.franklin
ruth.teitelbaum
sarah.mather
sophie.wilson
stephanie.kwolek
yvonne.brill

View File

@@ -1,3 +1 @@
INFRACLASS=scaleway
#SCW_INSTANCE_TYPE=DEV1-L
#SCW_ZONE=fr-par-2

View File

@@ -69,14 +69,11 @@ _cmd_deploy() {
echo deploying > tags/$TAG/status
sep "Deploying tag $TAG"
# If this VM image is using cloud-init,
# wait for cloud-init to be done
# Wait for cloudinit to be done
pssh "
if [ -d /var/lib/cloud ]; then
while [ ! -f /var/lib/cloud/instance/boot-finished ]; do
sleep 1
done
fi"
while [ ! -f /var/lib/cloud/instance/boot-finished ]; do
sleep 1
done"
# Special case for scaleway since it doesn't come with sudo
if [ "$INFRACLASS" = "scaleway" ]; then
@@ -105,12 +102,6 @@ _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 <lib/postprep.py
pssh --timeout 900 --send-input "python /tmp/postprep.py >>/tmp/pp.out 2>>/tmp/pp.err" <tags/$TAG/ips.txt
@@ -217,14 +208,7 @@ _cmd_kube() {
echo 'alias k=kubectl' | sudo tee /etc/bash_completion.d/k &&
echo 'complete -F __start_kubectl k' | sudo tee -a /etc/bash_completion.d/k"
# Disable swap
# (note that this won't survive across node reboots!)
if [ "$INFRACLASS" = "linode" ]; then
pssh "
sudo swapoff -a"
fi
# Initialize kube control plane
# Initialize kube master
pssh --timeout 200 "
if i_am_first_node && [ ! -f /etc/kubernetes/admin.conf ]; then
kubeadm token generate > /tmp/token &&
@@ -598,7 +582,7 @@ _cmd_start() {
case "$1" in
--infra) INFRA=$2; shift 2;;
--settings) SETTINGS=$2; shift 2;;
--count) die "Flag --count is deprecated; please use --students instead." ;;
--count) COUNT=$2; shift 2;;
--tag) TAG=$2; shift 2;;
--students) STUDENTS=$2; shift 2;;
*) die "Unrecognized parameter: $1."
@@ -737,7 +721,7 @@ _cmd_helmprom() {
need_tag
pssh "
if i_am_first_node; then
sudo -u docker -H helm repo add prometheus-community https://prometheus-community.github.io/helm-charts/
sudo -u docker -H helm 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 \

View File

@@ -1,58 +0,0 @@
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]"
}

View File

@@ -5,9 +5,6 @@ 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'
@@ -16,6 +13,9 @@ 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 \
scw instance server delete zone=${SCW_ZONE} force-shutdown=true with-ip=true
xargs -n1 -P10 -I@@ \
scw instance server delete force-shutdown=true server-id=@@
}
scw_get_ids_by_tag() {
TAG=$1
scw instance server list zone=${SCW_ZONE} name=$TAG -o json | jq -r .[].id
scw instance server list name=$TAG -o json | jq -r .[].id
}
scw_get_ips_by_tag() {
TAG=$1
scw instance server list zone=${SCW_ZONE} name=$TAG -o json | jq -r .[].public_ip.address
scw instance server list name=$TAG -o json | jq -r .[].public_ip.address
}

View File

@@ -18,11 +18,11 @@ pssh() {
echo "[parallel-ssh] $@"
export PSSH=$(which pssh || which parallel-ssh)
case "$INFRACLASS" in
hetzner) LOGIN=root ;;
linode) LOGIN=root ;;
*) LOGIN=ubuntu ;;
esac
if [ "$INFRACLASS" = hetzner ]; then
LOGIN=root
else
LOGIN=ubuntu
fi
$PSSH -h $HOSTFILE -l $LOGIN \
--par 100 \

View File

@@ -2,11 +2,11 @@
"""
There are two ways to use this script:
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
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
2. Pass a domain as the 1st argument, and IP addresses then.
It will configure the domain with the listed IP addresses.
@@ -19,53 +19,55 @@ import requests
import sys
import yaml
# This can be tweaked if necessary.
# configurable stuff
domains_file = "../../plentydomains/domains.txt"
config_file = os.path.join(
os.environ["HOME"], ".config/gandi/config.yaml")
os.environ["HOME"], ".config/gandi/config.yaml")
tag = None
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"]
# 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)
# Now, do the work.
# 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)
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},
# )
#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.")

View File

@@ -2,7 +2,7 @@
#/ /kube-halfday.yml.html 200!
#/ /kube-fullday.yml.html 200!
#/ /kube-twodays.yml.html 200!
/ /lke.yml.html 200!
/ /kube-adv.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

View File

@@ -24,9 +24,14 @@
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
"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=="
},
"backo2": {
"version": "1.0.2",
@@ -34,19 +39,27 @@
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
"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"
}
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
"integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE="
},
"body-parser": {
"version": "1.18.2",
@@ -70,15 +83,20 @@
"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.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"component-inherit": {
"version": "0.0.3",
@@ -134,76 +152,58 @@
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
},
"engine.io": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
"integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz",
"integrity": "sha1-PQIRtwpVLOhB/8fahiezAamkFi4=",
"requires": {
"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"
"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"
},
"dependencies": {
"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==",
"accepts": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
"integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
"requires": {
"ms": "^2.1.1"
"mime-types": "2.1.17",
"negotiator": "0.6.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.5.0",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
"integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.4.tgz",
"integrity": "sha1-T88TcLRxY70s6b4nM5ckMDUNTqE=",
"requires": {
"component-emitter": "~1.3.0",
"component-emitter": "1.2.1",
"component-inherit": "0.0.3",
"debug": "~3.1.0",
"engine.io-parser": "~2.2.0",
"debug": "2.6.9",
"engine.io-parser": "2.1.1",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~7.4.2",
"xmlhttprequest-ssl": "~1.5.4",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"ws": "3.3.3",
"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.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
"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=",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.4",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
"arraybuffer.slice": "0.0.6",
"base64-arraybuffer": "0.1.5",
"blob": "0.0.4",
"has-binary2": "1.0.2"
}
},
"escape-html": {
@@ -278,9 +278,9 @@
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz",
"integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=",
"requires": {
"isarray": "2.0.1"
}
@@ -376,6 +376,11 @@
"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",
@@ -385,14 +390,20 @@
}
},
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
"requires": {
"better-assert": "1.0.2"
}
},
"parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
"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"
}
},
"parseurl": {
"version": "1.3.2",
@@ -476,104 +487,51 @@
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
},
"socket.io": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.0.tgz",
"integrity": "sha512-9UPJ1UTvKayuQfVv2IQ3k7tCQC/fboDyIK62i99dAQIyHKaBsNdTpwHLgKJ6guRWxRtC9H+138UwpaGuQO9uWQ==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz",
"integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=",
"requires": {
"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=="
}
"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"
}
},
"socket.io-adapter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
"integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs="
},
"socket.io-client": {
"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==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz",
"integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=",
"requires": {
"backo2": "1.0.2",
"base64-arraybuffer": "0.1.5",
"component-bind": "1.0.0",
"component-emitter": "~1.3.0",
"debug": "~3.1.0",
"engine.io-client": "~3.5.0",
"has-binary2": "~1.0.2",
"component-emitter": "1.2.1",
"debug": "2.6.9",
"engine.io-client": "3.1.4",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"socket.io-parser": "~3.3.0",
"object-component": "0.0.3",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"socket.io-parser": "3.1.2",
"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.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==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz",
"integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=",
"requires": {
"component-emitter": "1.2.1",
"debug": "~4.1.0",
"debug": "2.6.9",
"has-binary2": "1.0.2",
"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": {
@@ -595,6 +553,11 @@
"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",
@@ -605,20 +568,31 @@
"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": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
"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"
}
},
"xmlhttprequest-ssl": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz",
"integrity": "sha1-BPVgkVcks4kIhxXMDteBPpZ3v1c="
},
"yeast": {
"version": "0.1.2",

View File

@@ -3,6 +3,6 @@
"version": "0.0.1",
"dependencies": {
"express": "^4.16.2",
"socket.io": "^2.4.0"
"socket.io": "^2.0.4"
}
}

View File

@@ -329,4 +329,4 @@ This is ideal to debug regressions, do side-by-side comparisons, etc.
:EN:- Connecting services together with a *Compose file*
:FR:- Utiliser Compose pour décrire son environnement
:FR:- Écrire un *Compose file* pour connecter les services entre eux
:FR:- Écrire un *Compose file* pour connecter les services entre eux

View File

@@ -742,15 +742,3 @@ class: extra-details
* This may be used to access an internal package repository.
(But try to use a multi-stage build instead, if possible!)
???
:EN:Container networking essentials
:EN:- The Container Network Model
:EN:- Container isolation
:EN:- Service discovery
:FR:Mettre ses conteneurs en réseau
:FR:- Le "Container Network Model"
:FR:- Isolation des conteneurs
:FR:- *Service discovery*

View File

@@ -229,5 +229,10 @@ containers together without exposing their ports.
???
:EN:- Exposing single containers
:FR:- Exposer un conteneur isolé
:EN:Connecting containers
:EN:- Container networking basics
:EN:- Exposing a container
:FR:Connecter les conteneurs
:FR:- Description du modèle réseau des conteneurs
:FR:- Exposer un conteneur

View File

@@ -101,5 +101,5 @@ Success!
???
:EN:- Leveraging the build cache for faster builds
:EN:- The build cache
:FR:- Tirer parti du cache afin d'optimiser la vitesse de *build*

View File

@@ -424,7 +424,7 @@ services:
- In this chapter, we showed many ways to write Dockerfiles.
- These Dockerfiles use sometimes diametrically opposed techniques.
- These Dockerfiles use sometimes diametrally opposed techniques.
- Yet, they were the "right" ones *for a specific situation.*
@@ -434,12 +434,5 @@ services:
???
:EN:Optimizing images
:EN:- Dockerfile tips, tricks, and best practices
:EN:- Reducing build time
:EN:- Reducing image size
:FR:Optimiser ses images
:FR:- Bonnes pratiques, trucs et astuces
:FR:- Réduire le temps de build
:FR:- Réduire la taille des images
:FR:- Bonnes pratiques pour la construction des images

View File

@@ -82,12 +82,3 @@ Use cases:
* Those containers can communicate over their `lo` interface.
<br/>(i.e. one can bind to 127.0.0.1 and the others can connect to it.)
???
:EN:Advanced container networking
:EN:- Transparent network access with the "host" driver
:EN:- Sharing is caring with the "container" driver
:FR:Paramétrage réseau avancé
:FR:- Accès transparent au réseau avec le mode "host"
:FR:- Partage de la pile réseau avece le mode "container"

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 231 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 208 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 71 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 167 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -1,914 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1600"
height="900"
viewBox="0 0 1600 900"
version="1.1"
id="svg696"
sodipodi:docname="single-node-dev.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
enable-background="new">
<metadata
id="metadata700">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>how-does-k8s-work</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1080"
id="namedview698"
showgrid="false"
inkscape:zoom="0.64"
inkscape:cx="133.80574"
inkscape:cy="440.39529"
inkscape:window-x="0"
inkscape:window-y="1080"
inkscape:window-maximized="0"
inkscape:current-layer="how-does-k8s-work"
units="px"
inkscape:snap-object-midpoints="true"
inkscape:document-rotation="0" />
<title
id="title304">how-does-k8s-work</title>
<style
type="text/css"
id="style5"><![CDATA[
@font-face {
font-family: "Droid Serif";
src: url(https://fonts.gstatic.com/s/droidserif/v9/tDbI2oqRg1oM3QBjjcaDkOr9rAU.woff2) format("woff2");
}
]]></style>
<defs
id="defs483">
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4502"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4500"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4492"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4490"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3758"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path3756" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3586"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path3584" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2794"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2792" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2634"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2632" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2202"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2200" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2054"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2052" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2781"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2779" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2657"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path2655" />
</marker>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2327"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path2325"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2181"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path2179"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2026"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path2024"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1880"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path1878"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1725"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1723"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker1613"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1611"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<linearGradient
id="linearGradient15544"
osb:paint="solid">
<stop
style="stop-color:#f7fe9a;stop-opacity:1;"
offset="0"
id="stop15542" />
</linearGradient>
<marker
inkscape:stockid="TriangleOutS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker15078"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path15076"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker14924"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path14922"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6635"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path6633" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker6541"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path6539" />
</marker>
<marker
inkscape:stockid="TriangleInS"
orient="auto"
refY="0.0"
refX="0.0"
id="marker6297"
style="overflow:visible"
inkscape:isstock="true"
inkscape:collect="always">
<path
id="path6295"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
transform="scale(-0.2)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker4353"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS"
inkscape:collect="always">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1;fill:#cccccc;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path4351" />
</marker>
<filter
x="-0.039000001"
y="-0.096999995"
width="1.077"
height="1.181"
filterUnits="objectBoundingBox"
id="filter-1">
<feOffset
dx="0"
dy="2"
in="SourceAlpha"
result="shadowOffsetOuter1"
id="feOffset308" />
<feGaussianBlur
stdDeviation="2"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
id="feGaussianBlur310" />
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"
type="matrix"
in="shadowBlurOuter1"
result="shadowMatrixOuter1"
id="feColorMatrix312" />
<feMerge
id="feMerge318">
<feMergeNode
in="shadowMatrixOuter1"
id="feMergeNode314" />
<feMergeNode
in="SourceGraphic"
id="feMergeNode316" />
</feMerge>
</filter>
<filter
x="-0.039000001"
y="-0.096999995"
width="1.077"
height="1.181"
filterUnits="objectBoundingBox"
id="filter-1-3">
<feOffset
dx="0"
dy="2"
in="SourceAlpha"
result="shadowOffsetOuter1"
id="feOffset308-6" />
<feGaussianBlur
stdDeviation="2"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
id="feGaussianBlur310-7" />
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"
type="matrix"
in="shadowBlurOuter1"
result="shadowMatrixOuter1"
id="feColorMatrix312-5" />
<feMerge
id="feMerge318-3">
<feMergeNode
in="shadowMatrixOuter1"
id="feMergeNode314-5" />
<feMergeNode
in="SourceGraphic"
id="feMergeNode316-6" />
</feMerge>
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1101"
x="-0.023413722"
width="1.0468274"
y="-0.023627247"
height="1.0472545">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="6.3996521"
id="feGaussianBlur1103" />
</filter>
</defs>
<g
id="how-does-k8s-work"
style="display:inline;fill:none;fill-rule:evenodd;stroke:none;stroke-width:1"
transform="translate(240,90)">
<path
inkscape:connector-curvature="0"
id="path3461"
d="m 550.17888,-14.918735 c -5.79916,0.29836 -11.4811,1.76683 -16.7125,4.31926 L 305.41221,100.68854 c -11.95688,5.8319 -20.64156,16.86146 -23.59583,29.96674 l -56.2625,249.981 c -2.62478,11.6363 -0.48906,23.8532 5.92083,33.869 0.7693,1.2119 1.59668,2.3849 2.47917,3.515 l 157.85,200.44354 c 8.27676,10.5066 20.82591,16.6243 34.09583,16.6217 l 253.13751,-0.06 c 13.26496,0.01 25.81322,-6.0964 34.09583,-16.5919 L 870.92472,417.96068 c 8.28119,-10.5119 11.38389,-24.2726 8.42917,-37.384 l -56.35,-249.98098 c -2.95427,-13.10528 -11.63895,-24.13483 -23.59583,-29.96674 L 571.32472,-10.599475 c -6.58031,-3.21076 -13.85136,-4.69595 -21.14584,-4.31926 z"
style="display:inline;opacity:1;mix-blend-mode:overlay;vector-effect:none;fill:#4285f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.78722;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter1101)"
transform="matrix(1.1552713,0,0,1.1552713,-85.780113,43.857391)"
inkscape:label="control plane" />
<text
id="text3581"
y="763.69812"
x="502.07855"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:32.6588px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter-1)"
xml:space="preserve"
transform="matrix(1.4029438,0,0,1.4029438,-157.63347,-1100.6682)"
inkscape:label="control plane label"><tspan
y="763.69812"
x="502.07855"
id="tspan3579"
sodipodi:role="line">SINGLE-NODE CLUSTER (FOR DEVELOPMENT)</tspan></text>
<g
id="apiserver"
transform="translate(-160.72924,-102.29405)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,99.00177,261.15864)"
ry="5.617908"
y="135.0636"
x="427.27243"
height="125.52966"
width="231.99153"
id="rect3668"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
inkscape:label="API server" />
<text
id="text4504"
y="438.31876"
x="552.05261"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
y="438.31876"
x="552.05261"
id="tspan4502"
sodipodi:role="line">API server</tspan></text>
</g>
<g
id="controller-manager"
transform="translate(-200,22)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,205.00177,315.15864)"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
id="rect4506"
width="231.99153"
height="125.52966"
x="426.37198"
y="298.2099"
ry="5.617908"
inkscape:label="controller manager" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
x="656.36871"
y="606.2431"
id="text4510"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
id="tspan4508"
x="656.36871"
y="606.2431">controller</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
id="tspan4524"
sodipodi:role="line"
x="656.36871"
y="647.06647">manager</tspan></text>
</g>
<g
id="scheduler"
transform="translate(-100,-118)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,-4.99823,167.15864)"
ry="5.617908"
y="475.73566"
x="427.94846"
height="125.52966"
width="231.99153"
id="rect4512"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
inkscape:label="scheduler" />
<text
id="text4516"
y="628.66296"
x="447.62476"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
y="628.66296"
x="447.62476"
id="tspan4514"
sodipodi:role="line">scheduler</tspan></text>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
x="560.76428"
y="764.29028"
id="text1649"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
id="tspan1647"
x="560.76428"
y="764.29028">VM or container</tspan></text>
<text
id="text3666"
y="281.34979"
x="-192.40442"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;line-height:1.25;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79323"
xml:space="preserve"
inkscape:label="emojis"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:1.79323"
y="281.34979"
x="-192.40442"
id="tspan3664"
sodipodi:role="line">👩🏼‍💻👨🏾‍💻🤖</tspan></text>
<rect
transform="matrix(0.74849003,0,0,0.42877044,-44.82304,220.38115)"
y="149.33455"
x="-217.52838"
height="357.51495"
width="435.94931"
id="rect3662"
style="display:inline;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1-3)"
inkscape:label="terminal" />
<text
inkscape:label="commands"
id="text3656"
y="331.70175"
x="-189.80005"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35.8105px;line-height:1.25;font-family:Consolas;-inkscape-font-specification:'Consolas, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#e6e6e6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.895262"
xml:space="preserve"><tspan
style="stroke-width:0.895262"
y="331.70175"
x="-189.80005"
sodipodi:role="line"
id="tspan1145">$ kubectl ...</tspan></text>
<text
inkscape:label="thumbsup"
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;line-height:1.25;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.79323"
x="207.5956"
y="359.34979"
id="text5150"><tspan
sodipodi:role="line"
id="tspan5148"
x="207.5956"
y="359.34979"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:71.7295px;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:1.79323" /></text>
<path
inkscape:label="arrow kubectl"
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path1135"
d="m 198.47677,432.58136 237.89885,-0.39724"
style="display:none;vector-effect:none;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#marker1613);marker-end:url(#marker1725);paint-order:normal" />
<path
inkscape:label="arrow scheduler"
style="display:inline;fill:#cccccc;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker4353);paint-order:normal"
d="m 414.32586,398.25642 -3.32843,50.64656"
id="path4349"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:label="arrow controller manager"
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path6293"
d="m 469.44118,396.89114 20.93237,189.75864"
style="display:inline;fill:#cccccc;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker6297);paint-order:normal" />
<path
inkscape:label="node top"
style="vector-effect:none;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4502);paint-order:normal"
d="M 117.18356,331.36155 274.9691,326.32941"
id="path4488"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<g
id="etcd"
transform="translate(1.971505,-80.740088)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,99.00177,181.15864)"
style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
id="rect4518"
width="231.99153"
height="125.52966"
x="427.27246"
y="-4.9364014"
ry="5.617908"
inkscape:label="etcd" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.834657"
x="552.46265"
y="161.46683"
id="text4522"
transform="translate(0,80)"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
id="tspan4520"
x="552.46265"
y="161.46683">etcd</tspan></text>
<g
id="g3228"
inkscape:label="storage"
style="display:inline"
transform="matrix(0.24039167,0,0,0.24784672,397.27503,204.48707)">
<ellipse
cx="374.0946"
cy="234.48322"
rx="92.65731"
ry="25.358843"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="ellipse9871" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="rect9873"
d="M 281.43729,235.006 V -24.92734 H 466.75191 V 235.006"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<ellipse
ry="25.358843"
rx="92.65731"
cy="-24.750473"
cx="374.0946"
id="path9869"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
</g>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:'Noto Color Emoji';-inkscape-font-specification:'Noto Color Emoji, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
x="-314.87122"
y="76.790283"
id="text5696"><tspan
sodipodi:role="line"
id="tspan5694"
x="-314.87122"
y="76.790283" /></text>
<g
id="g6027"
transform="translate(173.26362,-123.65545)"
inkscape:label="kubelet">
<rect
transform="matrix(0.83465672,0,0,0.83465672,99.00177,261.15864)"
ry="5.617908"
y="135.0636"
x="427.27243"
height="125.52966"
width="231.99153"
id="rect6021"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
inkscape:label="rect" />
<text
id="text6025"
y="438.31876"
x="552.05261"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
y="438.31876"
x="552.05261"
id="tspan6023"
sodipodi:role="line">kubelet</tspan></text>
</g>
<g
id="g6159"
inkscape:label="pod"
style="display:inline"
transform="translate(-465.32975,388.44365)">
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,188.949 38.7689,-11.2425 38.7688,11.2425 -38.7688,11.24254 z"
id="path6139" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,193.25418 v 41.2523 l 36.1218,20.00898 0.1788,-50.46488 z"
id="path6141" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1254.6157,193.25418 v 41.2523 l -36.1217,20.00898 -0.1788,-50.46488 z"
id="path6143" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.0298px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.550744"
x="1195.4893"
y="274.76129"
id="text6147"><tspan
sodipodi:role="line"
id="tspan6145"
x="1195.4893"
y="274.76129"
style="stroke-width:0.550744">pod</tspan></text>
</g>
<g
id="g7100"
inkscape:label="container engine"
transform="translate(780.76442,-206.55137)">
<g
id="g7089"
transform="translate(-657.05924,68.771622)">
<rect
transform="matrix(0.83465672,0,0,0.83465672,205.00177,315.15864)"
style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.78697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter-1)"
id="rect7081"
width="231.99153"
height="125.52966"
x="426.37198"
y="298.2099"
ry="5.617908"
inkscape:label="controller manager" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.834657"
x="656.36871"
y="606.2431"
id="text7087"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
id="tspan7085"
sodipodi:role="line"
x="656.36871"
y="606.2431">container</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.6587px;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.834657"
sodipodi:role="line"
x="656.36871"
y="647.06647"
id="tspan7093">engine</tspan></text>
</g>
</g>
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path2048"
d="m 718.47061,417.59763 2.84701,-46.58932"
style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker2054);marker-end:url(#marker2202);paint-order:normal"
inkscape:label="node top left" />
<path
inkscape:label="arrow etcd"
style="display:inline;fill:#cccccc;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6635);paint-order:normal"
d="m 465.71622,277.64 30.73977,-64.3282"
id="path6537"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:label="node top"
style="display:inline;vector-effect:none;fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:20;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-start:url(#marker1880);marker-end:url(#marker2026);paint-order:normal"
d="m 505.55141,327.11713 103.53017,-3.03009"
id="path8569"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<g
id="g7154"
inkscape:label="pod"
style="display:inline"
transform="translate(-578.21351,370.84416)">
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,188.949 38.7689,-11.2425 38.7688,11.2425 -38.7688,11.24254 z"
id="path7144" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,193.25418 v 41.2523 l 36.1218,20.00898 0.1788,-50.46488 z"
id="path7146" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1254.6157,193.25418 v 41.2523 l -36.1217,20.00898 -0.1788,-50.46488 z"
id="path7148" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.0298px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.550744"
x="1195.4893"
y="274.76129"
id="text7152"><tspan
sodipodi:role="line"
id="tspan7150"
x="1195.4893"
y="274.76129"
style="stroke-width:0.550744">pod</tspan></text>
</g>
<g
id="g7166"
inkscape:label="pod"
style="display:inline"
transform="translate(-606.70424,238.95491)">
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,188.949 38.7689,-11.2425 38.7688,11.2425 -38.7688,11.24254 z"
id="path7156" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1177.078,193.25418 v 41.2523 l 36.1218,20.00898 0.1788,-50.46488 z"
id="path7158" />
<path
inkscape:export-ydpi="376.57999"
inkscape:export-xdpi="376.57999"
style="fill:#eeeeee;fill-rule:evenodd;stroke:#000000;stroke-width:2.74114;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0"
d="m 1254.6157,193.25418 v 41.2523 l -36.1217,20.00898 -0.1788,-50.46488 z"
id="path7160" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.0298px;line-height:1.25;font-family:'Droid Serif';-inkscape-font-specification:'Droid Serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.550744"
x="1195.4893"
y="274.76129"
id="text7164"><tspan
sodipodi:role="line"
id="tspan7162"
x="1195.4893"
y="274.76129"
style="stroke-width:0.550744">pod</tspan></text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 234 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -1,519 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
viewBox="16 32 880 495"
fill="none"
stroke="none"
stroke-linecap="square"
stroke-miterlimit="10"
id="svg464"
sodipodi:docname="k8s-net-1-pod-to-pod.svg"
width="1600"
height="900"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)">
<metadata
id="metadata470">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs468" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="476"
inkscape:window-height="1032"
id="namedview466"
showgrid="false"
inkscape:zoom="1.0208333"
inkscape:cx="480"
inkscape:cy="360"
inkscape:window-x="480"
inkscape:window-y="18"
inkscape:window-maximized="0"
inkscape:current-layer="svg464" />
<clipPath
id="p.0">
<path
d="M 0,0 H 960 V 720 H 0 Z"
clip-rule="nonzero"
id="path317" />
</clipPath>
<g
clip-path="url(#p.0)"
id="g462">
<path
fill="#000000"
fill-opacity="0"
d="M 0,0 H 960 V 720 H 0 Z"
fill-rule="evenodd"
id="path320" />
<path
fill="#d9d9d9"
d="m 66.944885,154.29968 v 0 c 0,-13.08115 10.604363,-23.68552 23.685509,-23.68552 H 296.55071 c 6.28177,0 12.30628,2.49544 16.74817,6.93734 4.4419,4.44189 6.93732,10.4664 6.93732,16.74818 v 94.73921 c 0,13.08116 -10.60434,23.6855 -23.68549,23.6855 H 90.630394 c -13.081146,0 -23.685509,-10.60434 -23.685509,-23.6855 z"
fill-rule="evenodd"
id="path322" />
<path
stroke="#434343"
stroke-width="2"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 66.944885,154.29968 v 0 c 0,-13.08115 10.604363,-23.68552 23.685509,-23.68552 H 296.55071 c 6.28177,0 12.30628,2.49544 16.74817,6.93734 4.4419,4.44189 6.93732,10.4664 6.93732,16.74818 v 94.73921 c 0,13.08116 -10.60434,23.6855 -23.68549,23.6855 H 90.630394 c -13.081146,0 -23.685509,-10.60434 -23.685509,-23.6855 z"
fill-rule="evenodd"
id="path324" />
<path
fill="#000000"
d="m 85.75713,154.61205 0.04687,1.23437 q 1.125,-1.40625 2.953125,-1.40625 3.125,0 3.15625,3.51563 v 6.51562 h -1.6875 v -6.51562 q -0.01563,-1.07813 -0.5,-1.57813 -0.46875,-0.51562 -1.484375,-0.51562 -0.8125,0 -1.4375,0.4375 -0.609375,0.4375 -0.96875,1.15625 v 7.01562 h -1.67187 v -9.85937 z m 8.246857,4.84375 q 0,-1.45313 0.5625,-2.60938 0.578125,-1.15625 1.59375,-1.78125 1.015625,-0.625 2.3125,-0.625 2.015623,0 3.250003,1.39063 1.25,1.39062 1.25,3.70312 v 0.125 q 0,1.4375 -0.54688,2.57813 -0.54687,1.14062 -1.57812,1.78125 -1.015628,0.64062 -2.359378,0.64062 -2,0 -3.25,-1.39062 -1.234375,-1.40625 -1.234375,-3.70313 z m 1.6875,0.20312 q 0,1.64063 0.765625,2.64063 0.765625,0.98437 2.03125,0.98437 1.296875,0 2.046878,-1 0.75,-1.01562 0.75,-2.82812 0,-1.625 -0.76563,-2.625 -0.765623,-1.01563 -2.046873,-1.01563 -1.25,0 -2.015625,1 -0.765625,0.98438 -0.765625,2.84375 z m 8.983643,-0.20312 q 0,-2.26563 1.07813,-3.64063 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17188 v -5.14063 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20312 q 0,1.67188 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39062 -2.32813,-1.39062 -1.23437,0 -1.9375,0.95312 -0.6875,0.95313 -0.6875,2.84375 z m 13.33397,5 q -2,0 -3.26563,-1.3125 -1.25,-1.32812 -1.25,-3.53125 v -0.3125 q 0,-1.46875 0.5625,-2.60937 0.5625,-1.15625 1.5625,-1.79688 1.01563,-0.65625 2.1875,-0.65625 1.92188,0 2.98438,1.26563 1.0625,1.26562 1.0625,3.625 v 0.6875 h -6.67188 q 0.0312,1.46875 0.84375,2.375 0.82813,0.89062 2.07813,0.89062 0.89062,0 1.51562,-0.35937 0.625,-0.375 1.07813,-0.96875 l 1.03125,0.79687 q -1.23438,1.90625 -3.71875,1.90625 z m -0.20313,-8.84375 q -1.01562,0 -1.71875,0.75 -0.6875,0.73438 -0.84375,2.07813 h 4.9375 v -0.125 q -0.0781,-1.28125 -0.70312,-1.98438 -0.60938,-0.71875 -1.67188,-0.71875 z"
fill-rule="nonzero"
id="path326" />
<path
fill="#000000"
fill-opacity="0"
d="M 741.52496,67.91863 H 819.2572"
fill-rule="evenodd"
id="path328" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="M 741.52496,67.91863 H 819.2572"
fill-rule="evenodd"
id="path330" />
<path
fill="#000000"
fill-opacity="0"
d="M 692.19946,70.157394 H 868.57745 V 116.01565 H 692.19946 Z"
fill-rule="evenodd"
id="path332" />
<path
fill="#000000"
d="m 747.46643,91.32864 q 0,2.078125 -0.96875,3.359375 -0.95313,1.28125 -2.57813,1.28125 -1.67187,0 -2.625,-1.0625 v 4.40625 h -1.5625 V 86.64114 h 1.42188 l 0.0781,1.015625 q 0.95313,-1.1875 2.65625,-1.1875 1.65625,0 2.60938,1.25 0.96875,1.234375 0.96875,3.453125 z m -1.5625,-0.1875 q 0,-1.546875 -0.67188,-2.4375 -0.65625,-0.90625 -1.8125,-0.90625 -1.42187,0 -2.125,1.265625 v 4.375 q 0.70313,1.25 2.15625,1.25 1.125,0 1.78125,-0.890625 0.67188,-0.890625 0.67188,-2.65625 z m 3.12799,0 q 0,-1.34375 0.53125,-2.421875 0.53125,-1.078125 1.46875,-1.65625 0.95313,-0.59375 2.15625,-0.59375 1.875,0 3.03125,1.296875 1.15625,1.296875 1.15625,3.453125 v 0.109375 q 0,1.328125 -0.51562,2.390625 -0.51563,1.0625 -1.46875,1.65625 -0.95313,0.59375 -2.1875,0.59375 -1.85938,0 -3.01563,-1.296875 -1.15625,-1.296875 -1.15625,-3.421875 z m 1.57813,0.1875 q 0,1.515625 0.70312,2.4375 0.70313,0.921875 1.89063,0.921875 1.20312,0 1.89062,-0.9375 0.70313,-0.9375 0.70313,-2.609375 0,-1.515625 -0.71875,-2.4375 -0.70313,-0.9375 -1.89063,-0.9375 -1.15625,0 -1.875,0.921875 -0.70312,0.921875 -0.70312,2.640625 z m 8.33557,-0.1875 q 0,-2.109375 1,-3.390625 1,-1.28125 2.625,-1.28125 1.60937,0 2.54687,1.109375 v -4.78125 h 1.5625 v 13 h -1.4375 l -0.0781,-0.984375 q -0.9375,1.15625 -2.60938,1.15625 -1.59375,0 -2.60937,-1.296875 -1,-1.3125 -1,-3.40625 z m 1.57812,0.1875 q 0,1.546875 0.64063,2.4375 0.64062,0.875 1.76562,0.875 1.5,0 2.1875,-1.34375 v -4.203125 q -0.70312,-1.296875 -2.15625,-1.296875 -1.15625,0 -1.79687,0.890625 -0.64063,0.890625 -0.64063,2.640625 z m 11.83496,-0.125 h -4.125 v -1.28125 h 4.125 z m 3.65546,-6.78125 v 2.21875 h 1.70312 v 1.21875 h -1.70312 v 5.671875 q 0,0.546875 0.21875,0.828125 0.23437,0.265625 0.78125,0.265625 0.26562,0 0.75,-0.09375 v 1.265625 q -0.625,0.171875 -1.20313,0.171875 -1.04687,0 -1.57812,-0.625 -0.53125,-0.640625 -0.53125,-1.8125 V 87.85989 h -1.67188 v -1.21875 h 1.67188 v -2.21875 z m 2.94427,6.71875 q 0,-1.34375 0.53125,-2.421875 0.53125,-1.078125 1.46875,-1.65625 0.95313,-0.59375 2.15625,-0.59375 1.875,0 3.03125,1.296875 1.15625,1.296875 1.15625,3.453125 v 0.109375 q 0,1.328125 -0.51562,2.390625 -0.51563,1.0625 -1.46875,1.65625 -0.95313,0.59375 -2.1875,0.59375 -1.85938,0 -3.01563,-1.296875 -1.15625,-1.296875 -1.15625,-3.421875 z m 1.57813,0.1875 q 0,1.515625 0.70312,2.4375 0.70313,0.921875 1.89063,0.921875 1.20312,0 1.89062,-0.9375 0.70313,-0.9375 0.70313,-2.609375 0,-1.515625 -0.71875,-2.4375 -0.70313,-0.9375 -1.89063,-0.9375 -1.15625,0 -1.875,0.921875 -0.70312,0.921875 -0.70312,2.640625 z m 11.9762,-0.125 h -4.125 v -1.28125 h 4.125 z m 9.26483,0.125 q 0,2.078125 -0.96875,3.359375 -0.95313,1.28125 -2.57813,1.28125 -1.67187,0 -2.625,-1.0625 v 4.40625 h -1.5625 V 86.64114 h 1.42188 l 0.0781,1.015625 q 0.95313,-1.1875 2.65625,-1.1875 1.65625,0 2.60938,1.25 0.96875,1.234375 0.96875,3.453125 z m -1.5625,-0.1875 q 0,-1.546875 -0.67188,-2.4375 -0.65625,-0.90625 -1.8125,-0.90625 -1.42187,0 -2.125,1.265625 v 4.375 q 0.70313,1.25 2.15625,1.25 1.125,0 1.78125,-0.890625 0.67188,-0.890625 0.67188,-2.65625 z m 3.12793,0 q 0,-1.34375 0.53125,-2.421875 0.53125,-1.078125 1.46875,-1.65625 0.95312,-0.59375 2.15625,-0.59375 1.875,0 3.03125,1.296875 1.15625,1.296875 1.15625,3.453125 v 0.109375 q 0,1.328125 -0.51563,2.390625 -0.51562,1.0625 -1.46875,1.65625 -0.95312,0.59375 -2.1875,0.59375 -1.85937,0 -3.01562,-1.296875 -1.15625,-1.296875 -1.15625,-3.421875 z m 1.57812,0.1875 q 0,1.515625 0.70313,2.4375 0.70312,0.921875 1.89062,0.921875 1.20313,0 1.89063,-0.9375 0.70312,-0.9375 0.70312,-2.609375 0,-1.515625 -0.71875,-2.4375 -0.70312,-0.9375 -1.89062,-0.9375 -1.15625,0 -1.875,0.921875 -0.70313,0.921875 -0.70313,2.640625 z m 8.33557,-0.1875 q 0,-2.109375 1,-3.390625 1,-1.28125 2.625,-1.28125 1.60938,0 2.54688,1.109375 v -4.78125 h 1.5625 v 13 h -1.4375 l -0.0781,-0.984375 q -0.9375,1.15625 -2.60937,1.15625 -1.59375,0 -2.60938,-1.296875 -1,-1.3125 -1,-3.40625 z m 1.57813,0.1875 q 0,1.546875 0.64062,2.4375 0.64063,0.875 1.76563,0.875 1.5,0 2.1875,-1.34375 v -4.203125 q -0.70313,-1.296875 -2.15625,-1.296875 -1.15625,0 -1.79688,0.890625 -0.64062,0.890625 -0.64062,2.640625 z"
fill-rule="nonzero"
id="path334" />
<path
fill="#f4cccc"
d="m 228.27559,151.61942 h 63.53264 l 8.34137,8.34138 v 41.70587 h -71.87401 z"
fill-rule="evenodd"
id="path336" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 228.27559,151.61942 h 63.53264 l 8.34137,8.34138 v 41.70587 h -71.87401 z"
fill-rule="evenodd"
id="path338" />
<path
fill="#000000"
d="m 246.86934,177.89761 q 0,2.25 -1.03125,3.625 -1.01563,1.375 -2.78125,1.375 -1.79688,0 -2.82813,-1.14063 v 4.75 h -1.67187 v -13.65625 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85938,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67188,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70312,-0.96875 -1.95312,-0.96875 -1.53125,0 -2.29688,1.35938 v 4.70312 q 0.76563,1.35938 2.32813,1.35938 1.20312,0 1.92187,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37307,0 q 0,-1.45312 0.5625,-2.60937 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54688,2.57812 -0.54687,1.14063 -1.57812,1.78125 -1.01563,0.64063 -2.35938,0.64063 -2,0 -3.25,-1.39063 -1.23437,-1.40625 -1.23437,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76562,2.64062 0.76563,0.98438 2.03125,0.98438 1.29688,0 2.04688,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76563,-2.625 -0.76562,-1.01562 -2.04687,-1.01562 -1.25,0 -2.01563,1 -0.76562,0.98437 -0.76562,2.84375 z m 8.98364,-0.20313 q 0,-2.26562 1.07813,-3.64062 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39063 -2.32813,-1.39063 -1.23437,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path340" />
<path
fill="#f4cccc"
d="m 180.86351,212.08398 h 63.53265 l 8.34137,8.34138 v 41.70586 h -71.87402 z"
fill-rule="evenodd"
id="path342" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 180.86351,212.08398 h 63.53265 l 8.34137,8.34138 v 41.70586 h -71.87402 z"
fill-rule="evenodd"
id="path344" />
<path
fill="#000000"
d="m 199.45726,238.36217 q 0,2.25 -1.03125,3.625 -1.01563,1.375 -2.78125,1.375 -1.79688,0 -2.82813,-1.14063 v 4.75 h -1.67187 v -13.65625 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85938,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67188,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70312,-0.96875 -1.95312,-0.96875 -1.53125,0 -2.29688,1.35938 v 4.70312 q 0.76563,1.35938 2.32813,1.35938 1.20312,0 1.92187,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37307,0 q 0,-1.45312 0.5625,-2.60937 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54688,2.57812 -0.54687,1.14063 -1.57812,1.78125 -1.01563,0.64063 -2.35938,0.64063 -2,0 -3.25,-1.39063 -1.23437,-1.40625 -1.23437,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76562,2.64062 0.76563,0.98438 2.03125,0.98438 1.29688,0 2.04688,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76563,-2.625 -0.76562,-1.01562 -2.04687,-1.01562 -1.25,0 -2.01563,1 -0.76562,0.98437 -0.76562,2.84375 z m 8.98364,-0.20313 q 0,-2.26562 1.07813,-3.64062 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39063 -2.32813,-1.39063 -1.23437,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path346" />
<path
fill="#d9d9d9"
d="m 398.08398,209.82724 v 0 c 0,-13.08115 10.60437,-23.6855 23.68552,-23.6855 h 205.92032 c 6.2818,0 12.30627,2.49542 16.74817,6.93732 4.44189,4.44189 6.93731,10.4664 6.93731,16.74818 v 94.73923 c 0,13.08115 -10.60437,23.68552 -23.68548,23.68552 H 421.7695 c -13.08115,0 -23.68552,-10.60437 -23.68552,-23.68552 z"
fill-rule="evenodd"
id="path348" />
<path
stroke="#434343"
stroke-width="2"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 398.08398,209.82724 v 0 c 0,-13.08115 10.60437,-23.6855 23.68552,-23.6855 h 205.92032 c 6.2818,0 12.30627,2.49542 16.74817,6.93732 4.44189,4.44189 6.93731,10.4664 6.93731,16.74818 v 94.73923 c 0,13.08115 -10.60437,23.68552 -23.68548,23.68552 H 421.7695 c -13.08115,0 -23.68552,-10.60437 -23.68552,-23.68552 z"
fill-rule="evenodd"
id="path350" />
<path
fill="#000000"
d="m 416.89624,210.1396 0.0469,1.23437 q 1.125,-1.40625 2.95313,-1.40625 3.125,0 3.15625,3.51563 v 6.51562 h -1.6875 v -6.51562 q -0.0156,-1.07813 -0.5,-1.57813 -0.46875,-0.51562 -1.48438,-0.51562 -0.8125,0 -1.4375,0.4375 -0.60937,0.4375 -0.96875,1.15625 v 7.01562 h -1.67187 v -9.85937 z m 8.24686,4.84375 q 0,-1.45313 0.5625,-2.60938 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39063 1.25,1.39062 1.25,3.70312 v 0.125 q 0,1.4375 -0.54688,2.57813 -0.54687,1.14062 -1.57812,1.78125 -1.01563,0.64062 -2.35938,0.64062 -2,0 -3.25,-1.39062 -1.23437,-1.40625 -1.23437,-3.70313 z m 1.6875,0.20312 q 0,1.64063 0.76562,2.64063 0.76563,0.98437 2.03125,0.98437 1.29688,0 2.04688,-1 0.75,-1.01562 0.75,-2.82812 0,-1.625 -0.76563,-2.625 -0.76562,-1.01563 -2.04687,-1.01563 -1.25,0 -2.01563,1 -0.76562,0.98438 -0.76562,2.84375 z m 8.98364,-0.20312 q 0,-2.26563 1.07812,-3.64063 1.07813,-1.375 2.8125,-1.375 1.73438,0 2.75,1.17188 v -5.14063 h 1.6875 v 14 h -1.54687 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70313,0 -2.79688,-1.40625 -1.07812,-1.40625 -1.07812,-3.65625 z m 1.6875,0.20312 q 0,1.67188 0.6875,2.625 0.70312,0.9375 1.92187,0.9375 1.60938,0 2.34375,-1.4375 v -4.53125 q -0.76562,-1.39062 -2.32812,-1.39062 -1.23438,0 -1.9375,0.95312 -0.6875,0.95313 -0.6875,2.84375 z m 13.33395,5 q -2,0 -3.26562,-1.3125 -1.25,-1.32812 -1.25,-3.53125 v -0.3125 q 0,-1.46875 0.5625,-2.60937 0.5625,-1.15625 1.5625,-1.79688 1.01562,-0.65625 2.1875,-0.65625 1.92187,0 2.98437,1.26563 1.0625,1.26562 1.0625,3.625 v 0.6875 h -6.67187 q 0.0312,1.46875 0.84375,2.375 0.82812,0.89062 2.07812,0.89062 0.89063,0 1.51563,-0.35937 0.625,-0.375 1.07812,-0.96875 l 1.03125,0.79687 q -1.23437,1.90625 -3.71875,1.90625 z m -0.20312,-8.84375 q -1.01563,0 -1.71875,0.75 -0.6875,0.73438 -0.84375,2.07813 h 4.9375 v -0.125 q -0.0781,-1.28125 -0.70313,-1.98438 -0.60937,-0.71875 -1.67187,-0.71875 z"
fill-rule="nonzero"
id="path352" />
<path
fill="#f4cccc"
d="m 436.5315,244.97638 h 63.53265 l 8.34137,8.34137 v 41.70587 H 436.5315 Z"
fill-rule="evenodd"
id="path354" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 436.5315,244.97638 h 63.53265 l 8.34137,8.34137 v 41.70587 H 436.5315 Z"
fill-rule="evenodd"
id="path356" />
<path
fill="#000000"
d="m 455.12524,271.25458 q 0,2.25 -1.03125,3.625 -1.01562,1.375 -2.78125,1.375 -1.79687,0 -2.82812,-1.14063 v 4.75 h -1.67188 V 266.2077 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85937,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67187,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70313,-0.96875 -1.95313,-0.96875 -1.53125,0 -2.29687,1.35938 v 4.70312 q 0.76562,1.35938 2.32812,1.35938 1.20313,0 1.92188,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37307,0 q 0,-1.45312 0.5625,-2.60937 0.57813,-1.15625 1.59375,-1.78125 1.01563,-0.625 2.3125,-0.625 2.01563,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54687,2.57812 -0.54688,1.14063 -1.57813,1.78125 -1.01562,0.64063 -2.35937,0.64063 -2,0 -3.25,-1.39063 -1.23438,-1.40625 -1.23438,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76563,2.64062 0.76562,0.98438 2.03125,0.98438 1.29687,0 2.04687,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76562,-2.625 -0.76563,-1.01562 -2.04688,-1.01562 -1.25,0 -2.01562,1 -0.76563,0.98437 -0.76563,2.84375 z m 8.98365,-0.20313 q 0,-2.26562 1.07812,-3.64062 1.07813,-1.375 2.8125,-1.375 1.73438,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54687 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70313,0 -2.79688,-1.40625 -1.07812,-1.40625 -1.07812,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70312,0.9375 1.92187,0.9375 1.60938,0 2.34375,-1.4375 v -4.53125 q -0.76562,-1.39063 -2.32812,-1.39063 -1.23438,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path358" />
<path
fill="#d9d9d9"
d="m 95.60105,380.54904 v 0 c 0,-13.08115 10.60436,-23.68552 23.68551,-23.68552 h 205.92032 c 6.2818,0 12.30627,2.49543 16.74817,6.93732 4.44192,4.4419 6.93735,10.4664 6.93735,16.7482 v 94.7392 c 0,13.08115 -10.60437,23.68552 -23.68552,23.68552 H 119.28656 c -13.08115,0 -23.68551,-10.60437 -23.68551,-23.68552 z"
fill-rule="evenodd"
id="path360" />
<path
stroke="#434343"
stroke-width="2"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 95.60105,380.54904 v 0 c 0,-13.08115 10.60436,-23.68552 23.68551,-23.68552 h 205.92032 c 6.2818,0 12.30627,2.49543 16.74817,6.93732 4.44192,4.4419 6.93735,10.4664 6.93735,16.7482 v 94.7392 c 0,13.08115 -10.60437,23.68552 -23.68552,23.68552 H 119.28656 c -13.08115,0 -23.68551,-10.60437 -23.68551,-23.68552 z"
fill-rule="evenodd"
id="path362" />
<path
fill="#000000"
d="m 114.4133,380.8614 0.0469,1.23437 q 1.125,-1.40625 2.95312,-1.40625 3.125,0 3.15625,3.51563 v 6.51562 h -1.6875 v -6.51562 q -0.0156,-1.07813 -0.5,-1.57813 -0.46875,-0.51562 -1.48437,-0.51562 -0.8125,0 -1.4375,0.4375 -0.60938,0.4375 -0.96875,1.15625 v 7.01562 h -1.67188 v -9.85937 z m 8.24686,4.84375 q 0,-1.45313 0.5625,-2.60938 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39063 1.25,1.39062 1.25,3.70312 v 0.125 q 0,1.4375 -0.54688,2.57813 -0.54687,1.14062 -1.57812,1.78125 -1.01563,0.64062 -2.35938,0.64062 -2,0 -3.25,-1.39062 -1.23437,-1.40625 -1.23437,-3.70313 z m 1.6875,0.20312 q 0,1.64063 0.76562,2.64063 0.76563,0.98437 2.03125,0.98437 1.29688,0 2.04688,-1 0.75,-1.01562 0.75,-2.82812 0,-1.625 -0.76563,-2.625 -0.76562,-1.01563 -2.04687,-1.01563 -1.25,0 -2.01563,1 -0.76562,0.98438 -0.76562,2.84375 z m 8.98364,-0.20312 q 0,-2.26563 1.07812,-3.64063 1.07813,-1.375 2.8125,-1.375 1.73438,0 2.75,1.17188 v -5.14063 h 1.6875 v 14 h -1.54687 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70313,0 -2.79688,-1.40625 -1.07812,-1.40625 -1.07812,-3.65625 z m 1.6875,0.20312 q 0,1.67188 0.6875,2.625 0.70312,0.9375 1.92187,0.9375 1.60938,0 2.34375,-1.4375 v -4.53125 q -0.76562,-1.39062 -2.32812,-1.39062 -1.23438,0 -1.9375,0.95312 -0.6875,0.95313 -0.6875,2.84375 z m 13.33397,5 q -2,0 -3.26563,-1.3125 -1.25,-1.32812 -1.25,-3.53125 v -0.3125 q 0,-1.46875 0.5625,-2.60937 0.5625,-1.15625 1.5625,-1.79688 1.01563,-0.65625 2.1875,-0.65625 1.92188,0 2.98438,1.26563 1.0625,1.26562 1.0625,3.625 v 0.6875 h -6.67188 q 0.0312,1.46875 0.84375,2.375 0.82813,0.89062 2.07813,0.89062 0.89062,0 1.51562,-0.35937 0.625,-0.375 1.07813,-0.96875 l 1.03125,0.79687 q -1.23438,1.90625 -3.71875,1.90625 z m -0.20313,-8.84375 q -1.01562,0 -1.71875,0.75 -0.6875,0.73438 -0.84375,2.07813 h 4.9375 v -0.125 q -0.0781,-1.28125 -0.70312,-1.98438 -0.60938,-0.71875 -1.67188,-0.71875 z"
fill-rule="nonzero"
id="path364" />
<path
fill="#f4cccc"
d="m 200.92651,377.43307 h 63.53262 l 8.3414,8.34137 v 41.70587 h -71.87402 z"
fill-rule="evenodd"
id="path366" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 200.92651,377.43307 h 63.53262 l 8.3414,8.34137 v 41.70587 h -71.87402 z"
fill-rule="evenodd"
id="path368" />
<path
fill="#000000"
d="m 219.52026,403.71124 q 0,2.25 -1.03125,3.625 -1.01563,1.375 -2.78125,1.375 -1.79688,0 -2.82813,-1.14063 v 4.75 h -1.67187 v -13.65625 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85938,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67188,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70312,-0.96875 -1.95312,-0.96875 -1.53125,0 -2.29688,1.35938 v 4.70312 q 0.76563,1.35938 2.32813,1.35938 1.20312,0 1.92187,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37307,0 q 0,-1.45312 0.5625,-2.60937 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54688,2.57812 -0.54687,1.14063 -1.57812,1.78125 -1.01563,0.64063 -2.35938,0.64063 -2,0 -3.25,-1.39063 -1.23437,-1.40625 -1.23437,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76562,2.64062 0.76563,0.98438 2.03125,0.98438 1.29688,0 2.04688,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76563,-2.625 -0.76562,-1.01562 -2.04687,-1.01562 -1.25,0 -2.01563,1 -0.76562,0.98437 -0.76562,2.84375 z m 8.98364,-0.20313 q 0,-2.26562 1.07813,-3.64062 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39063 -2.32813,-1.39063 -1.23437,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path370" />
<path
fill="#000000"
fill-opacity="0"
d="m 145.48819,305.3176 v 0 c 0,-108.95232 87.37144,-197.2756 195.1496,-197.2756 v 0 c 51.7569,0 101.39395,20.78433 137.99161,57.78069 36.59766,36.99635 57.15802,87.17416 57.15802,139.49492 v 0 c 0,108.9523 -87.37146,197.27557 -195.14963,197.27557 v 0 c -107.77815,0 -195.1496,-88.32327 -195.1496,-197.27557 z"
fill-rule="evenodd"
id="path372" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 145.48819,305.3176 v 0 c 0,-108.95232 87.37144,-197.2756 195.1496,-197.2756 v 0 c 51.7569,0 101.39395,20.78433 137.99161,57.78069 36.59766,36.99635 57.15802,87.17416 57.15802,139.49492 v 0 c 0,108.9523 -87.37146,197.27557 -195.14963,197.27557 v 0 c -107.77815,0 -195.1496,-88.32327 -195.1496,-197.27557 z"
fill-rule="evenodd"
id="path374" />
<path
fill="#f4cccc"
d="m 108.9895,417.0105 h 63.53264 l 8.34137,8.34137 v 41.70587 H 108.9895 Z"
fill-rule="evenodd"
id="path376" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 108.9895,417.0105 h 63.53264 l 8.34137,8.34137 v 41.70587 H 108.9895 Z"
fill-rule="evenodd"
id="path378" />
<path
fill="#000000"
d="m 127.58325,443.2887 q 0,2.25 -1.03125,3.625 -1.01562,1.375 -2.78125,1.375 -1.79687,0 -2.82812,-1.14063 v 4.75 h -1.67188 v -13.65625 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85937,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67187,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70313,-0.96875 -1.95313,-0.96875 -1.53125,0 -2.29687,1.35938 v 4.70312 q 0.76562,1.35938 2.32812,1.35938 1.20313,0 1.92188,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37306,0 q 0,-1.45312 0.5625,-2.60937 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54688,2.57812 -0.54687,1.14063 -1.57812,1.78125 -1.01563,0.64063 -2.35938,0.64063 -2,0 -3.25,-1.39063 -1.23437,-1.40625 -1.23437,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76562,2.64062 0.76563,0.98438 2.03125,0.98438 1.29688,0 2.04688,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76563,-2.625 -0.76562,-1.01562 -2.04687,-1.01562 -1.25,0 -2.01563,1 -0.76562,0.98437 -0.76562,2.84375 z m 8.98364,-0.20313 q 0,-2.26562 1.07813,-3.64062 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39063 -2.32813,-1.39063 -1.23437,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path380" />
<path
fill="#f4cccc"
d="m 87.96063,209.9895 h 63.53264 l 8.34137,8.34137 v 41.70587 H 87.96063 Z"
fill-rule="evenodd"
id="path382" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 87.96063,209.9895 h 63.53264 l 8.34137,8.34137 v 41.70587 H 87.96063 Z"
fill-rule="evenodd"
id="path384" />
<path
fill="#000000"
d="m 106.55438,236.26768 q 0,2.25 -1.03125,3.625 -1.01563,1.375 -2.78125,1.375 -1.79688,0 -2.828125,-1.14062 v 4.75 H 98.24188 v -13.65625 h 1.53125 l 0.07813,1.09375 q 1.03124,-1.26563 2.85937,-1.26563 1.78125,0 2.8125,1.34375 1.03125,1.32813 1.03125,3.71875 z m -1.67188,-0.20312 q 0,-1.65625 -0.71875,-2.625 -0.70312,-0.96875 -1.95312,-0.96875 -1.53125,0 -2.296875,1.35937 v 4.70313 q 0.765625,1.35937 2.328125,1.35937 1.20312,0 1.92187,-0.95312 0.71875,-0.96875 0.71875,-2.875 z m 3.37307,0 q 0,-1.45313 0.5625,-2.60938 0.57812,-1.15625 1.59375,-1.78125 1.01562,-0.625 2.3125,-0.625 2.01562,0 3.25,1.39063 1.25,1.39062 1.25,3.70312 v 0.125 q 0,1.4375 -0.54688,2.57813 -0.54687,1.14062 -1.57812,1.78125 -1.01563,0.64062 -2.35938,0.64062 -2,0 -3.25,-1.39062 -1.23437,-1.40625 -1.23437,-3.70313 z m 1.6875,0.20312 q 0,1.64063 0.76562,2.64063 0.76563,0.98437 2.03125,0.98437 1.29688,0 2.04688,-1 0.75,-1.01562 0.75,-2.82812 0,-1.625 -0.76563,-2.625 -0.76562,-1.01563 -2.04687,-1.01563 -1.25,0 -2.01563,1 -0.76562,0.98438 -0.76562,2.84375 z m 8.98364,-0.20312 q 0,-2.26563 1.07813,-3.64063 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17188 v -5.14063 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20312 q 0,1.67188 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39062 -2.32813,-1.39062 -1.23437,0 -1.9375,0.95312 -0.6875,0.95313 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path386" />
<path
fill="#000000"
fill-opacity="0"
d="m 204.99213,171.03674 -81.10236,38.96063"
fill-rule="evenodd"
id="path388" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 204.99213,171.03674 -81.10236,38.96063"
fill-rule="evenodd"
id="path390" />
<path
fill="#000000"
fill-opacity="0"
d="m 185.52843,162.97461 31.27559,49.10237"
fill-rule="evenodd"
id="path392" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 185.52843,162.97461 31.27559,49.10237"
fill-rule="evenodd"
id="path394" />
<path
fill="#000000"
fill-opacity="0"
d="m 182.18898,171.03674 46.07873,5.60631"
fill-rule="evenodd"
id="path396" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 182.18898,171.03674 46.07873,5.60631"
fill-rule="evenodd"
id="path398" />
<path
fill="#000000"
fill-opacity="0"
d="m 180.86351,442.03412 44,27.2756"
fill-rule="evenodd"
id="path400" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 180.86351,442.03412 44,27.2756"
fill-rule="evenodd"
id="path402" />
<path
fill="#000000"
fill-opacity="0"
d="M 236.86351,427.48032 208.73753,469.3386"
fill-rule="evenodd"
id="path404" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="M 236.86351,427.48032 208.73753,469.3386"
fill-rule="evenodd"
id="path406" />
<path
fill="#000000"
fill-opacity="0"
d="m 228.2021,461.26248 h 24.53543"
fill-rule="evenodd"
id="path408" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 228.2021,461.26248 h 24.53543"
fill-rule="evenodd"
id="path410" />
<path
fill="#000000"
fill-opacity="0"
d="m 521.6842,219.98076 35.21259,20.59843"
fill-rule="evenodd"
id="path412" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 521.6842,219.98076 35.21259,20.59843"
fill-rule="evenodd"
id="path414" />
<path
fill="#000000"
fill-opacity="0"
d="m 505.5599,219.98076 -33.10236,25.00789"
fill-rule="evenodd"
id="path416" />
<path
stroke="#0000ff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 505.5599,219.98076 -33.10236,25.00789"
fill-rule="evenodd"
id="path418" />
<path
fill="#0000ff"
d="m 205.39896,461.26248 v 0 c 0,-6.29694 5.10466,-11.40158 11.40157,-11.40158 v 0 c 3.02389,0 5.92392,1.20123 8.06213,3.33945 2.13821,2.13818 3.33945,5.03823 3.33945,8.06213 v 0 c 0,6.29691 -5.10466,11.40155 -11.40158,11.40155 v 0 c -6.29691,0 -11.40157,-5.10465 -11.40157,-11.40155 z"
fill-rule="evenodd"
id="path420" />
<path
fill="#000000"
fill-opacity="0"
d="m 208.73839,453.20035 16.12427,16.12424 m 0,-16.12424 -16.12427,16.12424"
fill-rule="evenodd"
id="path422" />
<path
fill="#000000"
fill-opacity="0"
d="m 205.39896,461.26248 v 0 c 0,-6.29694 5.10466,-11.40158 11.40157,-11.40158 v 0 c 3.02389,0 5.92392,1.20123 8.06213,3.33945 2.13821,2.13818 3.33945,5.03823 3.33945,8.06213 v 0 c 0,6.29691 -5.10466,11.40155 -11.40158,11.40155 v 0 c -6.29691,0 -11.40157,-5.10465 -11.40157,-11.40155 z"
fill-rule="evenodd"
id="path424" />
<path
stroke="#ffffff"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 208.73839,453.20035 16.12427,16.12424 m 0,-16.12424 -16.12427,16.12424"
fill-rule="evenodd"
id="path426" />
<path
stroke="#ffffff"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 205.39896,461.26248 v 0 c 0,-6.29694 5.10466,-11.40158 11.40157,-11.40158 v 0 c 3.02389,0 5.92392,1.20123 8.06213,3.33945 2.13821,2.13818 3.33945,5.03823 3.33945,8.06213 v 0 c 0,6.29691 -5.10466,11.40155 -11.40158,11.40155 v 0 c -6.29691,0 -11.40157,-5.10465 -11.40157,-11.40155 z"
fill-rule="evenodd"
id="path428" />
<path
fill="#0000ff"
d="m 182.18898,171.03674 v 0 c 0,-6.29692 5.10466,-11.40157 11.40157,-11.40157 v 0 c 3.02389,0 5.92392,1.20124 8.06213,3.33944 2.13821,2.13821 3.33945,5.03825 3.33945,8.06213 v 0 c 0,6.29692 -5.10466,11.40158 -11.40158,11.40158 v 0 c -6.29691,0 -11.40157,-5.10466 -11.40157,-11.40158 z"
fill-rule="evenodd"
id="path430" />
<path
fill="#000000"
fill-opacity="0"
d="m 185.52843,162.97461 16.12425,16.12427 m 0,-16.12427 -16.12425,16.12427"
fill-rule="evenodd"
id="path432" />
<path
fill="#000000"
fill-opacity="0"
d="m 182.18898,171.03674 v 0 c 0,-6.29692 5.10466,-11.40157 11.40157,-11.40157 v 0 c 3.02389,0 5.92392,1.20124 8.06213,3.33944 2.13821,2.13821 3.33945,5.03825 3.33945,8.06213 v 0 c 0,6.29692 -5.10466,11.40158 -11.40158,11.40158 v 0 c -6.29691,0 -11.40157,-5.10466 -11.40157,-11.40158 z"
fill-rule="evenodd"
id="path434" />
<path
stroke="#ffffff"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 185.52843,162.97461 16.12425,16.12427 m 0,-16.12427 -16.12425,16.12427"
fill-rule="evenodd"
id="path436" />
<path
stroke="#ffffff"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 182.18898,171.03674 v 0 c 0,-6.29692 5.10466,-11.40157 11.40157,-11.40157 v 0 c 3.02389,0 5.92392,1.20124 8.06213,3.33944 2.13821,2.13821 3.33945,5.03825 3.33945,8.06213 v 0 c 0,6.29692 -5.10466,11.40158 -11.40158,11.40158 v 0 c -6.29691,0 -11.40157,-5.10466 -11.40157,-11.40158 z"
fill-rule="evenodd"
id="path438" />
<path
fill="#0000ff"
d="m 502.22046,211.91864 v 0 c 0,-6.29692 5.10468,-11.40158 11.40161,-11.40158 v 0 c 3.02387,0 5.92389,1.20123 8.06214,3.33945 2.13818,2.13821 3.33941,5.03823 3.33941,8.06213 v 0 c 0,6.29691 -5.10467,11.40157 -11.40155,11.40157 v 0 c -6.29693,0 -11.40161,-5.10466 -11.40161,-11.40157 z"
fill-rule="evenodd"
id="path440" />
<path
fill="#000000"
fill-opacity="0"
d="m 505.5599,203.8565 16.1243,16.12425 m 0,-16.12425 -16.1243,16.12425"
fill-rule="evenodd"
id="path442" />
<path
fill="#000000"
fill-opacity="0"
d="m 502.22046,211.91864 v 0 c 0,-6.29692 5.10468,-11.40158 11.40161,-11.40158 v 0 c 3.02387,0 5.92389,1.20123 8.06214,3.33945 2.13818,2.13821 3.33941,5.03823 3.33941,8.06213 v 0 c 0,6.29691 -5.10467,11.40157 -11.40155,11.40157 v 0 c -6.29693,0 -11.40161,-5.10466 -11.40161,-11.40157 z"
fill-rule="evenodd"
id="path444" />
<path
stroke="#ffffff"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 505.5599,203.8565 16.1243,16.12425 m 0,-16.12425 -16.1243,16.12425"
fill-rule="evenodd"
id="path446" />
<path
stroke="#ffffff"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 502.22046,211.91864 v 0 c 0,-6.29692 5.10468,-11.40158 11.40161,-11.40158 v 0 c 3.02387,0 5.92389,1.20123 8.06214,3.33945 2.13818,2.13821 3.33941,5.03823 3.33941,8.06213 v 0 c 0,6.29691 -5.10467,11.40157 -11.40155,11.40157 v 0 c -6.29693,0 -11.40161,-5.10466 -11.40161,-11.40157 z"
fill-rule="evenodd"
id="path448" />
<path
fill="#f4cccc"
d="m 520.96063,240.58267 h 63.53265 l 8.34137,8.34138 v 41.70586 h -71.87402 z"
fill-rule="evenodd"
id="path450" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 520.96063,240.58267 h 63.53265 l 8.34137,8.34138 v 41.70586 h -71.87402 z"
fill-rule="evenodd"
id="path452" />
<path
fill="#000000"
d="m 539.5544,266.86087 q 0,2.25 -1.03125,3.625 -1.01563,1.375 -2.78125,1.375 -1.79688,0 -2.82813,-1.14063 v 4.75 h -1.67187 v -13.65625 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85938,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67188,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70312,-0.96875 -1.95312,-0.96875 -1.53125,0 -2.29688,1.35938 v 4.70312 q 0.76563,1.35938 2.32813,1.35938 1.20312,0 1.92187,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37305,0 q 0,-1.45312 0.5625,-2.60937 0.57813,-1.15625 1.59375,-1.78125 1.01563,-0.625 2.3125,-0.625 2.01563,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54687,2.57812 -0.54688,1.14063 -1.57813,1.78125 -1.01562,0.64063 -2.35937,0.64063 -2,0 -3.25,-1.39063 -1.23438,-1.40625 -1.23438,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76563,2.64062 0.76562,0.98438 2.03125,0.98438 1.29687,0 2.04687,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76562,-2.625 -0.76563,-1.01562 -2.04688,-1.01562 -1.25,0 -2.01562,1 -0.76563,0.98437 -0.76563,2.84375 z m 8.98364,-0.20313 q 0,-2.26562 1.07813,-3.64062 1.07812,-1.375 2.8125,-1.375 1.73437,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54688 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70312,0 -2.79687,-1.40625 -1.07813,-1.40625 -1.07813,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70313,0.9375 1.92188,0.9375 1.60937,0 2.34375,-1.4375 v -4.53125 q -0.76563,-1.39063 -2.32813,-1.39063 -1.23437,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path454" />
<path
fill="#f4cccc"
d="m 252.73753,436.23883 h 63.53264 l 8.34137,8.3414 v 41.70587 h -71.87401 z"
fill-rule="evenodd"
id="path456" />
<path
stroke="#434343"
stroke-width="1"
stroke-linejoin="round"
stroke-linecap="butt"
d="m 252.73753,436.23883 h 63.53264 l 8.34137,8.3414 v 41.70587 h -71.87401 z"
fill-rule="evenodd"
id="path458" />
<path
fill="#000000"
d="m 271.33127,462.51703 q 0,2.25 -1.03125,3.625 -1.01562,1.375 -2.78125,1.375 -1.79687,0 -2.82812,-1.14063 v 4.75 h -1.67188 v -13.65625 h 1.53125 l 0.0781,1.09375 q 1.03125,-1.26562 2.85937,-1.26562 1.78125,0 2.8125,1.34375 1.03125,1.32812 1.03125,3.71875 z m -1.67187,-0.20313 q 0,-1.65625 -0.71875,-2.625 -0.70313,-0.96875 -1.95313,-0.96875 -1.53125,0 -2.29687,1.35938 v 4.70312 q 0.76562,1.35938 2.32812,1.35938 1.20313,0 1.92188,-0.95313 0.71875,-0.96875 0.71875,-2.875 z m 3.37307,0 q 0,-1.45312 0.5625,-2.60937 0.57813,-1.15625 1.59375,-1.78125 1.01563,-0.625 2.3125,-0.625 2.01563,0 3.25,1.39062 1.25,1.39063 1.25,3.70313 v 0.125 q 0,1.4375 -0.54687,2.57812 -0.54688,1.14063 -1.57813,1.78125 -1.01562,0.64063 -2.35937,0.64063 -2,0 -3.25,-1.39063 -1.23438,-1.40625 -1.23438,-3.70312 z m 1.6875,0.20313 q 0,1.64062 0.76563,2.64062 0.76562,0.98438 2.03125,0.98438 1.29687,0 2.04687,-1 0.75,-1.01563 0.75,-2.82813 0,-1.625 -0.76562,-2.625 -0.76563,-1.01562 -2.04688,-1.01562 -1.25,0 -2.01562,1 -0.76563,0.98437 -0.76563,2.84375 z m 8.98365,-0.20313 q 0,-2.26562 1.07812,-3.64062 1.07813,-1.375 2.8125,-1.375 1.73438,0 2.75,1.17187 v -5.14062 h 1.6875 v 14 h -1.54687 l -0.0937,-1.0625 q -1,1.25 -2.8125,1.25 -1.70313,0 -2.79688,-1.40625 -1.07812,-1.40625 -1.07812,-3.65625 z m 1.6875,0.20313 q 0,1.67187 0.6875,2.625 0.70312,0.9375 1.92187,0.9375 1.60938,0 2.34375,-1.4375 v -4.53125 q -0.76562,-1.39063 -2.32812,-1.39063 -1.23438,0 -1.9375,0.95313 -0.6875,0.95312 -0.6875,2.84375 z"
fill-rule="nonzero"
id="path460" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,48 +1,3 @@
- date: [2021-02-08, 2021-02-10]
country: www
city: streaming
event: ENIX SAS
speaker: jpetazzo
title: Docker intensif (en français)
lang: fr
attend: https://enix.io/fr/services/formation/online/
- date: [2021-02-15, 2021-02-18]
country: www
city: streaming
event: ENIX SAS
speaker: jpetazzo
title: Fondamentaux Kubernetes (en français)
lang: fr
attend: https://enix.io/fr/services/formation/online/
- date: [2021-02-22, 2021-02-23]
country: www
city: streaming
event: ENIX SAS
speaker: jpetazzo
title: Packaging et CI/CD pour Kubernetes (en français)
lang: fr
attend: https://enix.io/fr/services/formation/online/
- date: [2021-02-24, 2021-02-26]
country: www
city: streaming
event: ENIX SAS
speaker: jpetazzo
title: Kubernetes avancé (en français)
lang: fr
attend: https://enix.io/fr/services/formation/online/
- date: [2021-03-01, 2021-03-02]
country: www
city: streaming
event: ENIX SAS
speaker: jpetazzo
title: Opérer Kubernetes (en français)
lang: fr
attend: https://enix.io/fr/services/formation/online/
- date: [2020-10-05, 2020-10-06]
country: www
city: streaming

View File

@@ -1,104 +0,0 @@
## Accessing our EKS cluster
- We also have a shared EKS cluster
- With individual IAM users
- Let's connect to this cluster!
---
## What we need
- `kubectl` (obviously!)
- `aws` CLI (recent-ish version)
(or `aws` CLI + `aws-iam-authenticator` plugin)
- AWS API access key and secret access key
- AWS region
- EKS cluster name
---
## Setting up AWS credentials
- There are many ways to do this
- We're going to use environment variables
- You're welcome to use whatever you like (e.g. AWS profiles)
.exercise[
- Set the AWS region, API access key, and secret key:
```bash
export AWS_DEFAULT_REGION=`us-east-2`
export AWS_ACCESS_KEY_ID=`AKI...`
export AWS_SECRET_ACCESS_KEY=`xyz123...`
```
- Check that the AWS API recognizes us:
```bash
aws sts get-caller-identity
```
]
---
## Updating our kubeconfig file
- Now we can use the AWS CLI to:
- obtain the Kubernetes API address
- register it in our kubeconfig file
.exercise[
- Update our kubeconfig file:
```bash
aws eks update-kubeconfig --name `fancy-clustername-1234`
```
- Run some harmless command:
```bash
kubectl version
```
]
---
## Our resources
- We have the following permissions:
- `view` in the `default` namespace
- `edit` in the `container-training` namespace
- `admin` in our personal namespace
- Our personal namespace is our IAM user name
(but with dots replaced with dashes)
- For instance, user `ada.lovelace` has namespace `ada-lovelace`
---
## Deploying things
- Let's deploy DockerCoins in our personal namespace!
- Expose the Web UI with a `LoadBalancer` service
???
:EN:- Working with an EKS cluster
:FR:- Travailler avec un cluster EKS

View File

@@ -1,13 +1,41 @@
# Accessing internal services
- When we are logged in on a cluster node, we can access internal services
(by virtue of the Kubernetes network model: all nodes can reach all pods and services)
- When we are accessing a remote cluster, things are different
(generally, our local machine won't have access to the cluster's internal subnet)
- How can we temporarily access a service without exposing it to everyone?
--
- `kubectl proxy`: gives us access to the API, which includes a proxy for HTTP resources
- `kubectl port-forward`: allows forwarding of TCP ports to arbitrary pods, services, ...
---
## Suspension of disbelief
The exercises in this section assume that we have set up `kubectl` on our
local machine in order to access a remote cluster.
We will therefore show how to access services and pods of the remote cluster,
from our local machine.
You can also run these exercises directly on the cluster (if you haven't
installed and set up `kubectl` locally).
Running commands locally will be less useful
(since you could access services and pods directly),
but keep in mind that these commands will work anywhere as long as you have
installed and set up `kubectl` to communicate with your cluster.
---
## `kubectl proxy` in theory
- Running `kubectl proxy` gives us access to the entire Kubernetes API
@@ -28,7 +56,7 @@
## `kubectl proxy` in practice
- Let's access the `web` service through `kubectl proxy`
- Let's access the `webui` service through `kubectl proxy`
.exercise[
@@ -37,9 +65,9 @@
kubectl proxy &
```
- Access the `web` service:
- Access the `webui` service:
```bash
curl localhost:8001/api/v1/namespaces/default/services/web/proxy/
curl localhost:8001/api/v1/namespaces/default/services/webui/proxy/index.html
```
- Terminate the proxy:
@@ -71,20 +99,22 @@
## `kubectl port-forward` in practice
- Let's access our remote NGINX server
- Let's access our remote Redis server
.exercise[
- Forward connections from local port 1234 to remote port 80:
- Forward connections from local port 10000 to remote port 6379:
```bash
kubectl port-forward svc/web 1234:80 &
kubectl port-forward svc/redis 10000:6379 &
```
- Connect to the NGINX server:
- Connect to the Redis server:
```bash
curl localhost:1234
telnet localhost 10000
```
- Issue a few commands, e.g. `INFO server` then `QUIT`
<!--
```wait Connected to localhost```
```keys INFO server```
@@ -104,17 +134,3 @@
: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)

View File

@@ -58,24 +58,27 @@
*probably aggregation layer*
---
## How are resources organized?
- Let's have a look at the Kubernetes API hierarchical structure
- We'll ask `kubectl` to show us the exacts requests that it's making
- Useful: `.metadata.selfLink` contains the URI of a resource
.exercise[
- Check the URI for a cluster-scope, "core" resource, e.g. a Node:
- Check the `apiVersion` and URI of a "core" resource, e.g. a Node:
```bash
kubectl -v6 get node node1
kubectl get nodes -o json | jq .items[0].apiVersion
kubectl get nodes -o json | jq .items[0].metadata.selfLink
```
- Check the URI for a cluster-scope, "non-core" resource, e.g. a ClusterRole:
- Get the `apiVersion` and URI for a "non-core" resource, e.g. a ClusterRole:
```bash
kubectl -v6 get clusterrole view
kubectl get clusterrole view -o json | jq .apiVersion
kubectl get clusterrole view -o json | jq .metadata.selfLink
```
]
@@ -120,17 +123,6 @@ 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:
```
@@ -176,7 +168,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 -r .items[0].metadata.name)
-o json | jq .items[0].metadata.name)
```
- Execute a command in a pod, showing the API requests:

View File

@@ -733,19 +733,17 @@ class: extra-details
## Figuring out who can do what
- For auditing purposes, sometimes we want to know who can perform which actions
- For auditing purposes, sometimes we want to know who can perform an action
- There are a few tools to help us with that, available as `kubectl` plugins:
- There are a few tools to help us with that
- `kubectl who-can` / [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
- [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
- `kubectl access-matrix` / [Rakkess (Review Access)](https://github.com/corneliusweig/rakkess) by Cornelius Weig
- [Review Access (aka Rakkess)](https://github.com/corneliusweig/rakkess)
- `kubectl rbac-lookup` / [RBAC Lookup](https://github.com/FairwindsOps/rbac-lookup) by FairwindsOps
- Both are available as standalone programs, or as plugins for `kubectl`
- `kubectl` plugins can be installed and managed with `krew`
- They can also be installed and executed as standalone programs
(`kubectl` plugins can be installed and managed with `krew`)
???

View File

@@ -10,7 +10,7 @@
- Jobs are great for "long" background work
("long" being at least minutes or hours)
("long" being at least minutes our hours)
- CronJobs are great to schedule Jobs at regular intervals

View File

@@ -58,20 +58,25 @@
.exercise[
- Let's install the cert-manager Helm chart with this one-liner:
- Create the namespace for cert-manager:
```bash
helm install cert-manager cert-manager \
--repo https://charts.jetstack.io \
--create-namespace --namespace cert-manager \
--set installCRDs=true
kubectl create ns cert-manager
```
- Add the Jetstack repository:
```bash
helm repo add jetstack https://charts.jetstack.io
```
- Install cert-manager:
```bash
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--set installCRDs=true
```
]
- If you prefer to install with a single YAML file, that's fine too!
(see [the documentation](https://cert-manager.io/docs/installation/kubernetes/#installing-with-regular-manifests) for instructions)
---
## ClusterIssuer manifest
@@ -84,15 +89,13 @@
## Creating the ClusterIssuer
- Download the file @@LINK[k8s/cm-clusterissuer.yaml]
(or copy-paste from the previous slide)
- The manifest shown on the previous slide is in @@LINK[k8s/cm-clusterissuer.yaml]
.exercise[
- Create the ClusterIssuer:
```bash
kubectl apply cm-clusterissuer.yaml
kubectl apply -f ~/container.training/k8s/cm-clusterissuer.yaml
```
]
@@ -115,9 +118,7 @@
## Creating the Certificate
- Download the file @@LINK[k8s/cm-certificate.yaml]
(or copy-paste from the previous slide)
- The manifest shown on the previous slide is in @@LINK[k8s/cm-certificate.yaml]
.exercise[
@@ -127,7 +128,7 @@
- Create the Certificate:
```bash
kubectl apply -f cm-certificate.yaml
kubectl apply -f ~/container.training/k8s/cm-certificate.yaml
```
]
@@ -174,14 +175,25 @@
---
## And then...
## What's missing ?
- A little bit later, we will have a `kubernetes.io/tls` Secret:
--
An Ingress Controller! 😅
.exercise[
- Install an Ingress Controller:
```bash
kubectl apply -f ~/container.training/k8s/traefik-v2.yaml
```
- Wait a little bit, and check that we now have a `kubernetes.io/tls` Secret:
```bash
kubectl get secrets
```
- Note that this might take a few minutes, because of the DNS integration!
]
---
@@ -211,23 +223,22 @@ spec:
class: extra-details
## Automatic TLS Ingress with annotations
## Let's Encrypt and nip.io
- It is also possible to annotate Ingress resources for cert-manager
- Let's Encrypt has [rate limits](https://letsencrypt.org/docs/rate-limits/) per domain
- If we annotate an Ingress resource with `cert-manager.io/cluster-issuer=xxx`:
(the limits only apply to the production environment, not staging)
- cert-manager will detect that annotation
- There is a limit of 50 certificates per registered domain
- it will obtain a certificate using the specified ClusterIssuer (`xxx`)
- If we try to use the production environment, we will probably hit the limit
- it will store the key and certificate in the specified Secret
- It's fine to use the staging environment for these experiments
- Note: the Ingress still needs the `tls` section with `secretName` and `hosts`
(our certs won't validate in a browser, but we can always check
the details of the cert to verify that it was issued by Let's Encrypt!)
???
:EN:- Obtaining certificates with cert-manager
:FR:- Obtenir des certificats avec cert-manager
:T: Obtaining TLS certificates with cert-manager

View File

@@ -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/docs/concepts/storage/volume-snapshots/)
- Option 3: [snapshots through Kubernetes API](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/)
(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)
(now in alpha for a few storage providers: GCE, OpenSDS, Ceph, Portworx)
---

View File

@@ -220,41 +220,6 @@ class: extra-details
---
class: pic
![](images/control-planes/single-node-dev.svg)
---
class: pic
![](images/control-planes/managed-kubernetes.svg)
---
class: pic
![](images/control-planes/single-control-and-workers.svg)
---
class: pic
![](images/control-planes/stacked-control-plane.svg)
---
class: pic
![](images/control-planes/non-dedicated-stacked-nodes.svg)
---
class: pic
![](images/control-planes/advanced-control-plane.svg)
---
class: pic
![](images/control-planes/advanced-control-plane-split-events.svg)
---
class: extra-details
## How many nodes should a cluster have?

View File

@@ -60,41 +60,21 @@
## Command-line arguments
- Indicate what should run in the container
- Pass options to `args` array in the container specification
- 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)):
- Example ([source](https://github.com/coreos/pods/blob/master/kubernetes.yaml#L29)):
```yaml
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"
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"
```
---
- The options can be passed directly to the program that we run ...
## `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
... or to a wrapper script that will use them to e.g. generate a config file
---
@@ -534,12 +514,73 @@ spec:
]
---
## Passwords, tokens, sensitive information
- For sensitive information, there is another special resource: *Secrets*
- Secrets and Configmaps work almost the same way
(we'll expose the differences on the next slide)
- The *intent* is different, though:
*"You should use secrets for things which are actually secret like API keys,
credentials, etc., and use config map for not-secret configuration data."*
*"In the future there will likely be some differentiators for secrets like rotation or support for backing the secret API w/ HSMs, etc."*
(Source: [the author of both features](https://stackoverflow.com/a/36925553/580281
))
---
class: extra-details
## Differences between configmaps and secrets
- Secrets are base64-encoded when shown with `kubectl get secrets -o yaml`
- keep in mind that this is just *encoding*, not *encryption*
- it is very easy to [automatically extract and decode secrets](https://medium.com/@mveritym/decoding-kubernetes-secrets-60deed7a96a3)
- [Secrets can be encrypted at rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
- With RBAC, we can authorize a user to access configmaps, but not secrets
(since they are two different kinds of resources)
---
class: extra-details
## Immutable ConfigMaps and Secrets
- Since Kubernetes 1.19, it is possible to mark a ConfigMap or Secret as *immutable*
```bash
kubectl patch configmap xyz --patch='{"immutable": true}'
```
- This brings performance improvements when using lots of ConfigMaps and Secrets
(lots = tens of thousands)
- Once a ConfigMap or Secret has been marked as immutable:
- its content cannot be changed anymore
- the `immutable` field can't be changed back either
- the only way to change it is to delete and re-create it
- Pods using it will have to be re-created as well
???
:EN:- Managing application configuration
:EN:- Exposing configuration with the downward API
:EN:- Exposing configuration with Config Maps
:EN:- Exposing configuration with Config Maps and Secrets
:FR:- Gérer la configuration des applications
:FR:- Configuration au travers de la *downward API*
:FR:- Configurer les applications avec des *Config Maps*
:FR:- Configuration via les *Config Maps* et *Secrets*

View File

@@ -92,29 +92,6 @@
---
## 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

View File

@@ -1,6 +1,6 @@
# Authoring YAML
- We have already generated YAML implicitly, with e.g.:
- There are various ways to generate YAML with Kubernetes, e.g.:
- `kubectl run`
@@ -32,63 +32,26 @@
---
## Various ways to write YAML
## We don't have to start from scratch
- Completely from scratch with our favorite editor
- Create a resource (e.g. Deployment)
(yeah, right)
- Dump its YAML with `kubectl get -o yaml ...`
- Dump an existing resource with `kubectl get -o yaml ...`
- Edit the YAML
(it is recommended to clean up the result)
- Use `kubectl apply -f ...` with the YAML file to:
- Ask `kubectl` to generate the YAML
- update the resource (if it's the same kind)
(with a `kubectl create --dry-run -o yaml`)
- create a new resource (if it's a different kind)
- Use The Docs, Luke
- Or: 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
@@ -100,18 +63,14 @@
kubectl create deployment web --image nginx --dry-run
```
- Optionally clean it up with `kubectl neat`, too
]
Note: in recent versions of Kubernetes, we should use `--dry-run=client`
- We can clean up that YAML even more if we want
(Or `--dry-run=server`; more on that later!)
(for instance, we can remove the `creationTimestamp` and empty dicts)
---
class: extra-details
## Using `--dry-run` with `kubectl apply`
- The `--dry-run` option can also be used with `kubectl apply`
@@ -128,8 +87,6 @@ class: extra-details
---
class: extra-details
## The limits of `kubectl apply --dry-run`
.exercise[
@@ -155,8 +112,6 @@ 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/)
@@ -180,8 +135,6 @@ 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
@@ -196,8 +149,6 @@ class: extra-details
---
class: extra-details
## `kubectl diff`
- Kubernetes 1.13 also introduced `kubectl diff`
@@ -258,8 +209,3 @@ 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

View File

@@ -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, you'll have to build/push your own images.)
(For level 3, fork the repository and use ctr.run to build images.)
See next slide if you need hints!
@@ -44,12 +44,20 @@ Also add `replicas: 5` to `values.yaml` to provide a default value.
## Changing the color
- Create an account on e.g. Docker Hub (e.g. `janedoe`)
- Fork the repository
- Create an image repository (e.g. `janedoe/web`)
- 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)
- Change the images and/or CSS in `web/static`
- Build and push
- Commit, push, trigger a rolling update
- Trigger a rolling update using the image you just pushed
(`imagePullPolicy` should be `Always`, which is the default)

View File

@@ -1,766 +0,0 @@
# 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!
---
## Install GitLab itself
- We will deploy GitLab with its official Helm chart
- It will still require a bunch of parameters and customization
- Brace!
---
## Installing the GitLab chart
```bash
helm repo add gitlab https://charts.gitlab.io/
DOMAIN=`cloudnative.party`
ISSUER=letsencrypt-production
helm upgrade --install gitlab gitlab/gitlab \
--create-namespace --namespace gitlab \
--set global.hosts.domain=$DOMAIN \
--set certmanager.install=false \
--set nginx-ingress.enabled=false \
--set global.ingress.class=traefik \
--set global.ingress.provider=traefik \
--set global.ingress.configureCertmanager=false \
--set global.ingress.annotations."cert-manager\.io/cluster-issuer"=$ISSUER \
--set gitlab.webservice.ingress.tls.secretName=gitlab-gitlab-tls \
--set registry.ingress.tls.secretName=gitlab-registry-tls \
--set minio.ingress.tls.secretName=gitlab-minio-tls
```
😰 Can we talk about all these parameters?
---
## Breaking down all these parameters
- `certmanager.install=false`
do not install cert-manager, we already have it
- `nginx-ingress.enabled=false`
do not install the NGINX ingress controller, we already have Traefik
- `global.ingress.class=traefik`, `global.ingress.provider=traefik`
these merely enable creation of Ingress resources
- `global.ingress.configureCertmanager=false`
do not create a cert-manager Issuer or ClusterIssuer, we have ours
---
## More parameters
- `global.ingress.annotations."cert-manager\.io/cluster-issuer"=$ISSUER`
this annotation tells cert-manager to automatically issue certs
- `gitlab.webservice.ingress.tls.secretName=gitlab-gitlab-tls`,
<br/>
`registry.ingress.tls.secretName=gitlab-registry-tls`,
<br/>
`minio.ingress.tls.secretName=gitlab-minio-tls`
these annotations enable TLS in the Ingress controller
---
## Wait for GitLab to come up
- Let's watch what's happening in the GitLab namespace:
```bash
watch kubectl get all --namespace gitlab
```
- We want to wait for all the Pods to be "Running" or "Completed"
- This will take a few minutes (10-15 minutes for me)
- Don't worry if you see Pods crashing and restarting
(it happens when they are waiting on a dependency which isn't up yet)
---
## Things that could go wrong
- Symptom: Pods remain "Pending" or "ContainerCreating" for a while
- Investigate these pods (with `kubectl describe pod ...`)
- Also look at events:
```bash
kubectl get events \
--field-selector=type=Warning --sort-by=metadata.creationTimestamp
```
- Make sure your cluster is big enough
(I use 3 `g6-standard-4` nodes)
---
## Log into GitLab
- First, let's check that we can connect to GitLab (with TLS):
`https://gitlab.$DOMAIN`
- It's asking us for a login and password!
- The login is `root`, and the password is stored in a Secret:
```bash
kubectl get secrets --namespace=gitlab gitlab-gitlab-initial-root-password \
-o jsonpath={.data.password} | base64 -d
```
---
## Configure GitLab
- For simplicity, we're going to use that "root" user
(but later, you can create multiple users, teams, etc.)
- First, let's add our SSH key
(top-right user menu → settings, then SSH keys on the left)
- Then, create a project
(using the + menu next to the search bar on top)
- Let's call it `kubecoin`
(you can change it, but you'll have to adjust Git paths later on)
---
## Try to push our repository
- This is the repository that we're going to use:
https://github.com/jpetazzo/kubecoin
- Let's clone that repository locally first:
```bash
git clone https://github.com/jpetazzo/kubecoin
```
- Add our GitLab instance as a remote:
```bash
git remote add gitlab git@gitlab.$DOMAIN:root/kubecoin.git
```
- Try to push:
```bash
git push -u gitlab
```
---
## Connection refused?
- Normally, we get the following error:
`port 22: Connection refused`
- Why? 🤔
--
- What does `gitlab.$DOMAIN` point to?
--
- Our Ingress Controller! (i.e. Traefik) 💡
- Our Ingress Controller has nothing to do with port 22
- So how do we solve this?
---
## Routing port 22
- Whatever is on `gitlab.$DOMAIN` needs to have the following "routing":
- port 80 → GitLab web service
- port 443 → GitLab web service, with TLS
- port 22 → GitLab shell service
- Currently, Traefik is managing `gitlab.$DOMAIN`
- We are going to tell Traefik to:
- accept connections on port 22
- send them to GitLab
---
## TCP routing
- The technique that we are going to use is specific to Traefik
- Other Ingress Controllers may or may not have similar features
- When they have similar features, they will be enabled very differently
---
## Telling Traefik to open port 22
- Let's reconfigure Traefik:
```bash
helm upgrade --install traefik traefik/traefik \
--create-namespace --namespace traefik \
--set "ports.websecure.tls.enabled=true" \
--set "providers.kubernetesIngress.publishedService.enabled=true" \
--set "ports.ssh.port=2222" \
--set "ports.ssh.exposedPort=22" \
--set "ports.ssh.expose=true" \
--set "ports.ssh.protocol=TCP"
```
- This creates a new "port" on Traefik, called "ssh", listening on port 22
- Internally, Traefik listens on port 2222 (for permission reasons)
- Note: Traefik docs also call these ports "entrypoints"
(these entrypoints are totally unrelated to the `ENTRYPOINT` in Dockerfiles)
---
## Knocking on port 22
- What happens if we try to connect to that port 22 right now?
```bash
curl gitlab.$DOMAIN:22
```
- We hit GitLab's web service!
- We need to tell Traefik what to do with connections to that port 22
- For that, we will create a "TCP route"
---
## Traefik TCP route
The following custom resource tells Traefik to route the `ssh` port that we
created earlier, to the `gitlab-gitlab-shell` service belonging to GitLab.
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitlab-shell
namespace: gitlab
spec:
entryPoints:
- ssh
routes:
- match: HostSNI(\`*`)
services:
- name: gitlab-gitlab-shell
port: 22
```
The `HostSNI` wildcard is the magic option to define a "default route".
---
## Creating the TCP route
Since our manifest has backticks, we must pay attention to quoting:
```bash
kubectl apply -f- << "EOF"
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitlab-shell
namespace: gitlab
spec:
entryPoints:
- ssh
routes:
- match: HostSNI(\`*`)
services:
- name: gitlab-gitlab-shell
port: 22
EOF
```
---
## Knocking on port 22, again
- Let's see what happens if we try port 22 now:
```bash
curl gitlab.$DOMAIN:22
```
- This should tell us something like `Received HTTP/0.9 when not allowed`
(because we're no longer talking to an HTTP server, but to SSH!)
- Try with SSH:
```bash
ssh git@gitlab.$DOMAIN
```
- After accepting the key fingerprint, we should see `Welcome to GitLab, @root!`
---
## Pushing again
- Now we can try to push our repository again:
```bash
git push -u gitlab
```
- Reload the project page in GitLab
- We should see our repository!
---
## CI/CD
- Click on the CI/CD tab on the left
(the one with the shuttle / space rocket icon)
- Our pipeline was detected...
- But it failed 😕
- Let's click on one of the failed jobs
- This is a permission issue!
---
## Fixing permissions
- GitLab needs to do a few of things in our cluster:
- create Pods to build our container images with BuildKit
- create Namespaces to deploy staging and production versions of our app
- create and update resources in these Namespaces
- For the time being, we're going to grant broad permissions
(and we will revisit and discuss what to do later)
---
## Granting permissions
- Let's give `cluster-admin` permissions to the GitLab ServiceAccount:
```bash
kubectl create clusterrolebinding gitlab \
--clusterrole=cluster-admin --serviceaccount=gitlab:default
```
- Then retry the CI/CD pipeline
- The build steps will now succeed; but the deploy steps will fail
- We need to set the `REGISTRY_USER` and `REGISTRY_PASSWORD` variables
- Let's explain what this is about!
---
## GitLab container registry access
- A registry access token is created for the duration of the CI/CD pipeline
(it is exposed through the `$CI_JOB_TOKEN` environment variable)
- This token gives access only to a specific repository in the registry
- It is valid only during the execution of the CI/CD pipeline
- We can (and we do!) use it to *push* images to the registry
- We cannot use it to *pull* images when running in staging or production
(because Kubernetes might need to pull images *after* the token expires)
- We need to create a separate read-only registry access token
---
## Creating the registry access token
- Let's go to "Settings" (the cog wheel on the left) / "Access Tokens"
- Create a token with `read_registry` permission
- Save the token name and the token value
- Then go to "Settings" / "CI/CD"
- In the "Variables" section, add two variables:
- `REGISTRY_USER` → token name
- `REGISTRY_PASSWORD` → token value
- Make sure that they are **not** protected!
(otherwise, they won't be available in non-default tags and branches)
---
## Trying again
- Go back to the CI/CD pipeline view, and hit "Retry"
- The deploy stage should now work correctly! 🎉
---
## Our CI/CD pipeline
- Let's have a look at the [.gitlab-ci.yml](https://github.com/jpetazzo/kubecoin/blob/107dac5066087c52747e557babc97e57f42dd71d/.gitlab-ci.yml) file
- We have multiple *stages*:
- lint (currently doesn't do much, it's mostly as an example)
- build (currently uses BuildKit)
- deploy
- "Deploy" behaves differently in staging and production
- Let's investigate that!
---
## Staging vs production
- In our pipeline, "production" means "a tag or branch named `production`"
(see the `except:` and `only:` sections)
- Everything else is "staging"
- In "staging":
- we build and push images
- we create a staging Namespace and deploy a copy of the app there
- In "production":
- we do not build anything
- we deploy (or update) a copy of the app in the production Namespace
---
## Namespace naming
- GitLab will create Namespaces named `gl-<user>-<project>-<hash>`
- At the end of the deployment, the web UI will be available at:
`http://<user>-<project>-<githash>-gitlab.<domain>`
- The "production" Namespace will be `<user>-<project>`
- And it will be available on its own domain as well:
`http://<project>-<githash>-gitlab.<domain>`
---
## Production
- `git tag -f production && git push -f --tags`
- Our CI/CD pipeline will deploy on the production URL
(`http://<user>-<project>-gitlab.<domain>`)
- It will do it *only* if that same git commit was pushed to staging first
(because the "production" pipeline skips the build phase)
---
## 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)
---
## Why not use GitLab's Kubernetes integration?
- "All-in-one" approach
(deploys its own Ingress, cert-manager, Prometheus, and much more)
- I wanted to show you something flexible and customizable instead
- But feel free to explore it now that we have shown the basics!
???
:EN:- CI/CD with GitLab
:FR:- CI/CD avec GitLab

View File

@@ -40,22 +40,7 @@
- a `Chart.yaml` file, containing metadata (name, version, description ...)
- Let's look at a simple chart for a basic demo app
---
## Adding the repo
- If you haven't done it before, you need to add the repo for that chart
.exercise[
- Add the repo that holds the chart for the OWASP Juice Shop:
```bash
helm repo add juice https://charts.securecodebox.io
```
]
- Let's look at a simple chart, `stable/tomcat`
---
@@ -65,17 +50,17 @@
.exercise[
- Download the tarball for `juice/juice-shop`:
- Download the tarball for `stable/tomcat`:
```bash
helm pull juice/juice-shop
helm pull stable/tomcat
```
(This will create a file named `juice-shop-X.Y.Z.tgz`.)
(This will create a file named `tomcat-X.Y.Z.tgz`.)
- Or, download + untar `juice/juice-shop`:
- Or, download + untar `stable/tomcat`:
```bash
helm pull juice/juice-shop --untar
helm pull stable/tomcat --untar
```
(This will create a directory named `juice-shop`.)
(This will create a directory named `tomcat`.)
]
@@ -83,13 +68,13 @@
## Looking at the chart's content
- Let's look at the files and directories in the `juice-shop` chart
- Let's look at the files and directories in the `tomcat` chart
.exercise[
- Display the tree structure of the chart we just downloaded:
```bash
tree juice-shop
tree tomcat
```
]
@@ -108,11 +93,12 @@ We see the components mentioned above: `Chart.yaml`, `templates/`, `values.yaml`
(using the standard Go template library)
.exercise[
- Look at the template file for the Service resource:
- Look at the template file for the tomcat Service resource:
```bash
cat juice-shop/templates/service.yaml
cat tomcat/templates/appsrv-svc.yaml
```
]
@@ -204,7 +190,7 @@ We see the components mentioned above: `Chart.yaml`, `templates/`, `values.yaml`
- At the top-level of the chart, it's a good idea to have a README
- It will be viewable with e.g. `helm show readme juice/juice-shop`
- It will be viewable with e.g. `helm show readme stable/tomcat`
- In the `templates/` directory, we can also have a `NOTES.txt` file

View File

@@ -1,338 +0,0 @@
# Charts using other charts
- Helm charts can have *dependencies* on other charts
- These dependencies will help us to share or reuse components
(so that we write and maintain less manifests, less templates, less code!)
- As an example, we will use a community chart for Redis
- This will help people who write charts, and people who use them
- ... And potentially remove a lot of code! ✌️
---
## Redis in DockerCoins
- In the DockerCoins demo app, we have 5 components:
- 2 internal webservices
- 1 worker
- 1 public web UI
- 1 Redis data store
- Every component is running some custom code, except Redis
- Every component is using a custom image, except Redis
(which is using the official `redis` image)
- Could we use a standard chart for Redis?
- Yes! Dependencies to the rescue!
---
## Adding our dependency
- First, we will add the dependency to the `Chart.yaml` file
- Then, we will ask Helm to download that dependency
- We will also *lock* the dependency
(lock it to a specific version, to ensure reproducibility)
---
## Declaring the dependency
- First, let's edit `Chart.yaml`
.exercise[
- In `Chart.yaml`, fill the `dependencies` section:
```yaml
dependencies:
- name: redis
version: 11.0.5
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
```
]
Where do that `repository` and `version` come from?
We're assuming here that we did our reserach,
or that our resident Helm expert advised us to
use Bitnami's Redis chart.
---
## Conditions
- The `condition` field gives us a way to enable/disable the dependency:
```yaml
conditions: redis.enabled
```
- Here, we can disable Redis with the Helm flag `--set redis.enabled=false`
(or set that value in a `values.yaml` file)
- Of course, this is mostly useful for *optional* dependencies
(otherwise, the app ends up being broken since it'll miss a component)
---
## Lock & Load!
- After adding the dependency, we ask Helm to pin an download it
.exercise[
- Ask Helm:
```bash
helm dependency update
```
(Or `helm dep up`)
]
- This wil create `Chart.lock` and fetch the dependency
---
## What's `Chart.lock`?
- This is a common pattern with dependencies
(see also: `Gemfile.lock`, `package.json.lock`, and many others)
- This lets us define loose dependencies in `Chart.yaml`
(e.g. "version 11.whatever, but below 12")
- But have the exact version used in `Chart.lock`
- This ensures reproducible deployments
- `Chart.lock` can (should!) be added to our source tree
- `Chart.lock` can (should!) regularly be updated
---
## Loose dependencies
- Here is an example of loose version requirement:
```yaml
dependencies:
- name: redis
version: ">=11 <12"
repository: https://charts.bitnami.com/bitnami
```
- This makes sure that we have the most recent version in the 11.x train
- ... But without upgrading to version 12.x
(because it might be incompatible)
---
## `build` vs `update`
- Helm actually offers two commands to manage dependencies:
`helm dependency build` = fetch dependencies listed in `Chart.lock`
`helm dependency update` = update `Chart.lock` (and run `build`)
- When the dependency gets updated, we can/should:
- `helm dep up` (update `Chart.lock` and fetch new chart)
- test!
- if everything is fine, `git add Chart.lock` and commit
---
## Where are my dependencies?
- Dependencies are downloaded to the `charts/` subdirectory
- When they're downloaded, they stay in compressed format (`.tgz`)
- Should we commit them to our code repository?
- Pros:
- more resilient to internet/mirror failures/decomissioning
- Cons:
- can add a lot of weight to the repo if charts are big or change often
- this can be solved by extra tools like git-lfs
---
## Dependency tuning
- DockerCoins expects the `redis` Service to be named `redis`
- Our Redis chart uses a different Service name by default
- Service name is `{{ template "redis.fullname" . }}-master`
- `redis.fullname` looks like this:
```
{{- define "redis.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
[...]
{{- end }}
{{- end }}
```
- How do we fix this?
---
## Setting dependency variables
- If we set `fullnameOverride` to `redis`:
- the `{{ template ... }}` block will output `redis`
- the Service name will be `redis-master`
- A parent chart can set values for its dependencies
- For example, in the parent's `values.yaml`:
```yaml
redis: # Name of the dependency
fullnameOverride: redis # Value passed to redis
cluster: # Other values passed to redis
enabled: false
```
- User can also set variables with `--set=` or with `--values=`
---
class: extra-details
## Passing templates
- We can even pass template `{{ include "template.name" }}`, but warning:
- need to be evaluated with the `tpl` function, on the child side
- evaluated in the context of the child, with no access to parent variables
<!-- FIXME this probably deserves an example, but I can't imagine one right now 😅 -->
---
## Getting rid of the `-master`
- Even if we set that `fullnameOverride`, the Service name will be `redis-master`
- To remove the `-master` suffix, we need to edit the chart itself
- To edit the Redis chart, we need to *embed* it in our own chart
- We need to:
- decompress the chart
- adjust `Chart.yaml` accordingly
---
## Embedding a dependency
.exercise[
- Decompress the chart:
```yaml
cd charts
tar zxf redis-*.tgz
cd ..
```
- Edit `Chart.yaml` and update the `dependencies` section:
```yaml
dependencies:
- name: redis
version: '*' # No need to constraint version, from local files
```
- Run `helm dep update`
]
---
## Updating the dependency
- Now we can edit the Service name
(it should be in `charts/redis/templates/redis-master-svc.yaml`)
- Then try to deploy the whole chart!
---
## Embedding a dependency multiple times
- What if we need multiple copies of the same subchart?
(for instance, if we need two completely different Redis servers)
- We can declare a dependency multiple times, and specify an `alias`:
```yaml
dependencies:
- name: redis
version: '*'
alias: querycache
- name: redis
version: '*'
alias: celeryqueue
```
- `.Chart.Name` will be set to the `alias`
---
class: extra-details
## Compatibility with Helm 2
- Chart `apiVersion: v1` is the only version supported by Helm 2
- Chart v1 is also supported by Helm 3
- Use v1 if you want to be compatible with Helm 2
- Instead of `Chart.yaml`, dependencies are defined in `requirements.yaml`
(and we should commit `requirements.lock` instead of `Chart.lock`)
???
:EN:- Depending on other charts
:EN:- Charts within charts
:FR:- Dépendances entre charts
:FR:- Un chart peut en cacher un autre

View File

@@ -1,84 +1,20 @@
# Managing stacks with Helm
- Helm is a (kind of!) package manager for Kubernetes
- We created our first resources with `kubectl run`, `kubectl expose` ...
- We can use it to:
- We have also created resources by loading YAML files with `kubectl apply -f`
- find existing packages (called "charts") created by other folks
- For larger stacks, managing thousands of lines of YAML is unreasonable
- install these packages, configuring them for our particular setup
- These YAML bundles need to be customized with variable parameters
- package our own things (for distribution or for internal use)
(E.g.: number of replicas, image version to use ...)
- manage the lifecycle of these installs (rollback to previous version etc.)
- It would be nice to have an organized, versioned collection of bundles
- It's a "CNCF graduate project", indicating a certain level of maturity
- It would be nice to be able to upgrade/rollback these bundles carefully
(more on that later)
---
## From `kubectl run` to YAML
- We can create resources with one-line commands
(`kubectl run`, `kubectl createa deployment`, `kubectl expose`...)
- We can also create resources by loading YAML files
(with `kubectl apply -f`, `kubectl create -f`...)
- There can be multiple resources in a single YAML files
(making them convenient to deploy entire stacks)
- However, these YAML bundles often need to be customized
(e.g.: number of replicas, image version to use, features to enable...)
---
## Beyond YAML
- Very often, after putting together our first `app.yaml`, we end up with:
- `app-prod.yaml`
- `app-staging.yaml`
- `app-dev.yaml`
- instructions indicating to users "please tweak this and that in the YAML"
- That's where using something like
[CUE](https://github.com/cuelang/cue/blob/v0.3.2/doc/tutorial/kubernetes/README.md),
[Kustomize](https://kustomize.io/),
or [Helm](https://helm.sh/) can help!
- Now we can do something like this:
```bash
helm install app ... --set this.parameter=that.value
```
---
## Other features of Helm
- With Helm, we create "charts"
- These charts can be used internally or distributed publicly
- Public charts can be indexed through the [Artifact Hub](https://artifacthub.io/)
- This gives us a way to find and install other folks' charts
- Helm also gives us ways to manage the lifecycle of what we install:
- keep track of what we have installed
- upgrade versions, change parameters, roll back, uninstall
- Furthermore, even if it's not "the" standard, it's definitely "a" standard!
- [Helm](https://helm.sh/) is an open source project offering all these things!
---
@@ -293,95 +229,55 @@ fine for personal and development clusters.)
---
class: extra-details
## Managing repositories
## How to find charts, the old way
- Let's check what repositories we have, and add the `stable` repo
- Helm 2 came with one pre-configured repo, the "stable" repo
(located at https://charts.helm.sh/stable)
- Helm 3 doesn't have any pre-configured repo
- The "stable" repo mentioned above is now being deprecated
- The new approach is to have fully decentralized repos
- Repos can be indexed in the Artifact Hub
(which supersedes the Helm Hub)
---
## How to find charts, the new way
- Go to the [Artifact Hub](https://artifacthub.io/packages/search?kind=0) (https://artifacthub.io)
- Or use `helm search hub ...` from the CLI
- Let's try to find a Helm chart for something called "OWASP Juice Shop"!
(it is a famous demo app used in security challenges)
---
## Finding charts from the CLI
- We can use `helm search hub <keyword>`
(the `stable` repo contains a set of official-ish charts)
.exercise[
- Look for the OWASP Juice Shop app:
- List our repos:
```bash
helm search hub owasp juice
helm repo list
```
- Since the URLs are truncated, try with the YAML output:
- Add the `stable` repo:
```bash
helm search hub owasp juice -o yaml
helm repo add stable https://kubernetes-charts.storage.googleapis.com/
```
]
Then go to → https://artifacthub.io/packages/helm/seccurecodebox/juice-shop
Adding a repo can take a few seconds (it downloads the list of charts from the repo).
It's OK to add a repo that already exists (it will merely update it).
---
## Finding charts on the web
## Search available charts
- We can also use the Artifact Hub search feature
- We can search available charts with `helm search`
- We need to specify where to search (only our repos, or Helm Hub)
- Let's search for all charts mentioning tomcat!
.exercise[
- Go to https://artifacthub.io/
- In the search box on top, enter "owasp juice"
- Click on the "juice-shop" result (not "multi-juicer" or "juicy-ctf")
]
---
## Installing the chart
- Click on the "Install" button, it will show instructions
.exercise[
- First, add the repository for that chart:
- Search for tomcat in the repo that we added earlier:
```bash
helm repo add juice https://charts.securecodebox.io
helm search repo tomcat
```
- Then, install the chart:
- Search for tomcat on the Helm Hub:
```bash
helm install my-juice-shop juice/juice-shop
helm search hub tomcat
```
]
Note: it is also possible to install directly a chart, with `--repo https://...`
[Helm Hub](https://hub.helm.sh/) indexes many repos, using the [Monocular](https://github.com/helm/monocular) server.
---
@@ -389,22 +285,22 @@ Note: it is also possible to install directly a chart, with `--repo https://...`
- "Installing a chart" means creating a *release*
- In the previous exemple, the release was named "my-juice-shop"
- We need to name that release
- We can also use `--generate-name` to ask Helm to generate a name for us
(or use the `--generate-name` to get Helm to generate one for us)
.exercise[
- Install the tomcat chart that we found earlier:
```bash
helm install java4ever stable/tomcat
```
- List the releases:
```bash
helm list
```
- Check that we have a `my-juice-shop-...` Pod up and running:
```bash
kubectl get pods
```
]
---
@@ -417,13 +313,13 @@ class: extra-details
- The `helm search` command only takes a search string argument
(e.g. `helm search juice-shop`)
(e.g. `helm search tomcat`)
- With Helm 2, the name is optional:
`helm install juice/juice-shop` will automatically generate a name
`helm install stable/tomcat` will automatically generate a name
`helm install --name my-juice-shop juice/juice-shop` will specify a name
`helm install --name java4ever stable/tomcat` will specify a name
---
@@ -437,12 +333,12 @@ class: extra-details
- List all the resources created by this release:
```bash
kubectl get all --selector=app.kubernetes.io/instance=my-juice-shop
kubectl get all --selector=release=java4ever
```
]
Note: this label wasn't added automatically by Helm.
Note: this `release` label wasn't added automatically by Helm.
<br/>
It is defined in that chart. In other words, not all charts will provide this label.
@@ -450,11 +346,11 @@ It is defined in that chart. In other words, not all charts will provide this la
## Configuring a release
- By default, `juice/juice-shop` creates a service of type `ClusterIP`
- By default, `stable/tomcat` creates a service of type `LoadBalancer`
- We would like to change that to a `NodePort`
- We could use `kubectl edit service my-juice-shop`, but ...
- We could use `kubectl edit service java4ever-tomcat`, but ...
... our changes would get overwritten next time we update that chart!
@@ -474,14 +370,14 @@ It is defined in that chart. In other words, not all charts will provide this la
.exercise[
- Look at the README for the app:
- Look at the README for tomcat:
```bash
helm show readme juice/juice-shop
helm show readme stable/tomcat
```
- Look at the values and their defaults:
```bash
helm show values juice/juice-shop
helm show values stable/tomcat
```
]
@@ -498,18 +394,18 @@ The `readme` may or may not have (accurate) explanations for the values.
- Values can be set when installing a chart, or when upgrading it
- We are going to update `my-juice-shop` to change the type of the service
- We are going to update `java4ever` to change the type of the service
.exercise[
- Update `my-juice-shop`:
- Update `java4ever`:
```bash
helm upgrade my-juice-shop juice/juice-shop --set service.type=NodePort
helm upgrade java4ever stable/tomcat --set service.type=NodePort
```
]
Note that we have to specify the chart that we use (`juice/my-juice-shop`),
Note that we have to specify the chart that we use (`stable/tomcat`),
even if we just want to update some values.
We can set multiple values. If we want to set many values, we can use `-f`/`--values` and pass a YAML file with all the values.
@@ -518,21 +414,25 @@ All unspecified values will take the default values defined in the chart.
---
## Connecting to the Juice Shop
## Connecting to tomcat
- Let's check the app that we just installed
- Let's check the tomcat server that we just installed
- Note: its readiness probe has a 60s delay
(so it will take 60s after the initial deployment before the service works)
.exercise[
- Check the node port allocated to the service:
```bash
kubectl get service my-juice-shop
PORT=$(kubectl get service my-juice-shop -o jsonpath={..nodePort})
kubectl get service java4ever-tomcat
PORT=$(kubectl get service java4ever-tomcat -o jsonpath={..nodePort})
```
- Connect to it:
- Connect to it, checking the demo app on `/sample/`:
```bash
curl localhost:$PORT/
curl localhost:$PORT/sample/
```
]
@@ -546,17 +446,3 @@ 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

View File

@@ -12,37 +12,22 @@
---
## Adding the repo
- If you haven't done it before, you need to add the repo for that chart
.exercise[
- Add the repo that holds the chart for the OWASP Juice Shop:
```bash
helm repo add juice https://charts.securecodebox.io
```
]
---
## We need a release
- We need to install something with Helm
- Let's use the `juice/juice-shop` chart as an example
- Let's use the `stable/tomcat` chart as an example
.exercise[
- Install a release called `orange` with the chart `juice/juice-shop`:
- Install a release called `tomcat` with the chart `stable/tomcat`:
```bash
helm upgrade orange juice/juice-shop --install
helm upgrade tomcat stable/tomcat --install
```
- Let's upgrade that release, and change a value:
```bash
helm upgrade orange juice/juice-shop --set ingress.enabled=true
helm upgrade tomcat stable/tomcat --set ingress.enabled=true
```
]
@@ -57,7 +42,7 @@
- View the history for that release:
```bash
helm history orange
helm history tomcat
```
]
@@ -97,11 +82,11 @@ We should see a number of secrets with TYPE `helm.sh/release.v1`.
.exercise[
- Examine the secret corresponding to the second release of `orange`:
- Examine the secret corresponding to the second release of `tomcat`:
```bash
kubectl describe secret sh.helm.release.v1.orange.v2
kubectl describe secret sh.helm.release.v1.tomcat.v2
```
(`v1` is the secret format; `v2` means revision 2 of the `orange` release)
(`v1` is the secret format; `v2` means revision 2 of the `tomcat` release)
]
@@ -117,7 +102,7 @@ There is a key named `release`.
- Dump the secret:
```bash
kubectl get secret sh.helm.release.v1.orange.v2 \
kubectl get secret sh.helm.release.v1.tomcat.v2 \
-o go-template='{{ .data.release }}'
```
@@ -135,7 +120,7 @@ Secrets are encoded in base64. We need to decode that!
- Decode the secret:
```bash
kubectl get secret sh.helm.release.v1.orange.v2 \
kubectl get secret sh.helm.release.v1.tomcat.v2 \
-o go-template='{{ .data.release | base64decode }}'
```
@@ -159,7 +144,7 @@ Let's try one more round of decoding!
- Decode it twice:
```bash
kubectl get secret sh.helm.release.v1.orange.v2 \
kubectl get secret sh.helm.release.v1.tomcat.v2 \
-o go-template='{{ .data.release | base64decode | base64decode }}'
```
@@ -179,7 +164,7 @@ Let's try one more round of decoding!
- Pipe the decoded release through `file -`:
```bash
kubectl get secret sh.helm.release.v1.orange.v2 \
kubectl get secret sh.helm.release.v1.tomcat.v2 \
-o go-template='{{ .data.release | base64decode | base64decode }}' \
| file -
```
@@ -200,7 +185,7 @@ Gzipped data! It can be decoded with `gunzip -c`.
- Rerun the previous command, but with `| gunzip -c > release-info` :
```bash
kubectl get secret sh.helm.release.v1.orange.v2 \
kubectl get secret sh.helm.release.v1.tomcat.v2 \
-o go-template='{{ .data.release | base64decode | base64decode }}' \
| gunzip -c > release-info
```
@@ -226,7 +211,7 @@ If we inspect that JSON (e.g. with `jq keys release-info`), we see:
- `config` (contains the values that we've set)
- `info` (date of deployment, status messages)
- `manifest` (YAML generated from the templates)
- `name` (name of the release, so `orange`)
- `name` (name of the release, so `tomcat`)
- `namespace` (namespace where we deployed the release)
- `version` (revision number within that release; starts at 1)

View File

@@ -1,191 +0,0 @@
# Helm and invalid values
- A lot of Helm charts let us specify an image tag like this:
```bash
helm install ... --set image.tag=v1.0
```
- What happens if we make a small mistake, like this:
```bash
helm install ... --set imagetag=v1.0
```
- Or even, like this:
```bash
helm install ... --set image=v1.0
```
🤔
---
## Making mistakes
- In the first case:
- we set `imagetag=v1.0` instead of `image.tag=v1.0`
- Helm will ignore that value (if it's not used anywhere in templates)
- the chart is deployed with the default value instead
- In the second case:
- we set `image=v1.0` instead of `image.tag=v1.0`
- `image` will be a string instead of an object
- Helm will *probably* fail when trying to evaluate `image.tag`
---
## Preventing mistakes
- To prevent the first mistake, we need to tell Helm:
*"let me know if any additional (unknonw) value was set!"*
- To prevent the second mistake, we need to tell Helm:
*"`image` should be an object, and `image.tag` should be a string!"*
- We can do this with *values schema validation*
---
## Helm values schema validation
- We can write a spec representing the possible values accepted by the chart
- Helm will check the validity of the values before trying to install/upgrade
- If it finds problems, it will stop immediately
- The spec uses [JSON Schema](https://json-schema.org/):
*JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.*
- JSON Schema is designed for JSON, but can easily work with YAML too
(or any language with `map|dict|associativearray` and `list|array|sequence|tuple`)
---
## In practice
- We need to put the JSON Schema spec in a file called `values.schema.json`
(at the root of our chart; right next to `values.yaml` etc.)
- The file is optional
- We don't need to register or declare it in `Chart.yaml` or anywhere
- Let's write a schema that will verify that ...
- `image.repository` is an official image (string without slashes or dots)
- `image.pullPolicy` can only be `Always`, `Never`, `IfNotPresent`
---
## `values.schema.json`
```json
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"pattern": "^[a-z0-9-_]+$"
},
"pullPolicy": {
"type": "string",
"pattern": "^(Always|Never|IfNotPresent)$"
}
}
}
}
}
```
---
## Testing our schema
- Let's try to install a couple releases with that schema!
.exercise[
- Try an invalid `pullPolicy`:
```bash
helm install broken --set image.pullPolicy=ShallNotPass
```
- Try an invalid value:
```bash
helm install should-break --set ImAgeTAg=toto
```
]
- The first one fails, but the second one still passes ...
- Why?
---
## Bailing out on unkown properties
- We told Helm what properties (values) were valid
- We didn't say what to do about additional (unknown) properties!
- We can fix that with `"additionalProperties": false`
.exercise[
- Edit `values.schema.json` to add `"additionalProperties": false`
```json
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
...
```
]
---
## Testing with unknown properties
.exercise[
- Try to pass an extra property:
```bash
helm install should-break --set ImAgeTAg=toto
```
- Try to pass an extra nested property:
```bash
helm install does-it-work --set image.hello=world
```
]
The first command should break.
The second will not.
`"additionalProperties": false` needs to be specified at each level.
???
:EN:- Helm schema validation
:FR:- Validation de schema Helm

View File

@@ -72,7 +72,7 @@
- Deploy DockerCoins, and scale up the `worker` Deployment:
```bash
kubectl apply -f ~/container.training/k8s/dockercoins.yaml
kubectl apply -f ~/container.training/k8/dockercoins.yaml
kubectl scale deployment worker --replicas=10
```
@@ -118,7 +118,7 @@
- Deploy `httplat`:
```bash
kubectl create deployment httplat --image=jpetazzo/httplat -- httplat http://rng/
kubectl create deployment 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/customscaling/services/httplat/httplat_latency_seconds
/namespaces/coins/services/httplat/httplat_latency_seconds
```
Also check the logs of the `prometheus-adapter` and the `kube-controller-manager`.

View File

@@ -1,151 +0,0 @@
# Kubernetes Internal APIs
- Almost every Kubernetes component has some kind of internal API
(some components even have multiple APIs on different ports!)
- At the very least, these can be used for healthchecks
(you *should* leverage this if you are deploying and operating Kubernetes yourself!)
- Sometimes, they are used internally by Kubernetes
(e.g. when the API server retrieves logs from kubelet)
- Let's review some of these APIs!
---
## API hunting guide
This is how we found and investigated these APIs:
- look for open ports on Kubernetes nodes
(worker nodes or control plane nodes)
- check which process owns that port
- probe the port (with `curl` or other tools)
- read the source code of that process
(in particular when looking for API routes)
OK, now let's see the results!
---
## etcd
- 2379/tcp → etcd clients
- should be HTTPS and require mTLS authentication
- 2380/tcp → etcd peers
- should be HTTPS and require mTLS authentication
- 2381/tcp → etcd healthcheck
- HTTP without authentication
- exposes two API routes: `/health` and `/metrics`
---
## kubelet
- 10248/tcp → healthcheck
- HTTP without authentication
- exposes a single API route, `/healthz`, that just returns `ok`
- 10250/tcp → internal API
- should be HTTPS and require mTLS authentication
- used by the API server to obtain logs, `kubectl exec`, etc.
---
class: extra-details
## kubelet API
- We can authenticate with e.g. our TLS admin certificate
- The following routes should be available:
- `/healthz`
- `/configz` (serves kubelet configuration)
- `/metrics`
- `/pods` (returns *desired state*)
- `/runningpods` (returns *current state* from the container runtime)
- `/logs` (serves files from `/var/log`)
- `/containerLogs/<namespace>/<podname>/<containername>` (can add e.g. `?tail=10`)
- `/run`, `/exec`, `/attach`, `/portForward`
- See [kubelet source code](https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/server/server.go) for details!
---
class: extra-details
## Trying the kubelet API
The following example should work on a cluster deployed with `kubeadm`.
1. Obtain the key and certificate for the `cluster-admin` user.
2. Log into a node.
3. Copy the key and certificate on the node.
4. Find out the name of the `kube-proxy` pod running on that node.
5. Run the following command, updating the pod name:
```bash
curl -d cmd=ls -k --cert admin.crt --key admin.key \
https://localhost:10250/run/kube-system/`kube-proxy-xy123`/kube-proxy
```
... This should show the content of the root directory in the pod.
---
## kube-proxy
- 10249/tcp → healthcheck
- HTTP, without authentication
- exposes a few API routes: `/healthz` (just returns `ok`), `/configz`, `/metrics`
- 10256/tcp → another healthcheck
- HTTP, without authentication
- also exposes a `/healthz` API route (but this one shows a timestamp)
---
## kube-controller and kube-scheduler
- 10257/tcp → kube-controller
- HTTPS, with optional mTLS authentication
- `/healthz` doesn't require authentication
- ... but `/configz` and `/metrics` do (use e.g. admin key and certificate)
- 10259/tcp → kube-scheduler
- similar to kube-controller, with the same routes
???
:EN:- Kubernetes internal APIs
:FR:- Les APIs internes de Kubernetes

View File

@@ -1,141 +0,0 @@
# 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

View File

@@ -52,7 +52,7 @@
- There are literally dozens of implementations out there
(https://github.com/containernetworking/cni/ lists more than 25 plugins)
(15 are listed in the Kubernetes documentation)
- Pods have level 3 (IP) connectivity, but *services* are level 4 (TCP or UDP)
@@ -128,36 +128,6 @@ 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

View File

@@ -321,9 +321,9 @@ class: extra-details
- Try to apply a few color labels:
```bash
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
kubectl label test-color-2 color=purple
kubectl label test-color-2 color=red
kubectl label test-color-2 color=blue --overwrite
```
]
@@ -432,9 +432,9 @@ class: extra-details
- Try to apply a few color labels:
```bash
kubectl label pod test-color-3 color=purple
kubectl label pod test-color-3 color=red
kubectl label pod test-color-3 color-
kubectl label test-color-3 color=purple
kubectl label test-color-3 color=red
kubectl label test-color-3 color-
```
]

View File

@@ -427,34 +427,26 @@ troubleshoot easily, without having to poke holes in our firewall.
---
## Tools and resources
- [Cilium Network Policy Editor](https://editor.cilium.io/)
- [Tufin Network Policy Viewer](https://orca.tufin.io/netpol/)
- 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
## Further resources
- 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: <!-- ##VERSION## -->
- [NetworkPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#networkpolicy-v1-networking-k8s-io)
- [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.20/#networkpolicyspec-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.20/#networkpolicyingressrule-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/):
- 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
???
:EN:- Isolating workloads with Network Policies

View File

@@ -1,521 +0,0 @@
# OpenEBS
- [OpenEBS] is a popular open-source storage solution for Kubernetes
- Uses the concept of "Container Attached Storage"
(1 volume = 1 dedicated controller pod + a set of replica pods)
- Supports a wide range of storage engines:
- LocalPV: local volumes (hostpath or device), no replication
- Jiva: for lighter workloads with basic cloning/snapshotting
- cStor: more powerful engine that also supports resizing, RAID, disk pools ...
- [Mayastor]: newer, even more powerful engine with NVMe and vhost-user support
[OpenEBS]: https://openebs.io/
[Mayastor]: https://github.com/openebs/MayaStor#mayastor
---
class: extra-details
## What are all these storage engines?
- LocalPV is great if we want good performance, no replication, easy setup
(it is similar to the Rancher local path provisioner)
- Jiva is great if we want replication and easy setup
(data is stored in containers' filesystems)
- cStor is more powerful and flexible, but requires more extensive setup
- Mayastor is designed to achieve extreme performance levels
(with the right hardware and disks)
- The OpenEBS documentation has a [good comparison of engines] to help us pick
[good comparison of engines]: https://docs.openebs.io/docs/next/casengines.html#cstor-vs-jiva-vs-localpv-features-comparison
---
## Installing OpenEBS with Helm
- The OpenEBS control plane can be installed with Helm
- It will run as a set of containers on Kubernetes worker nodes
.exercise[
- Install OpenEBS:
```bash
helm upgrade --install openebs openebs \
--repo https://openebs.github.io/charts \
--namespace openebs --create-namespace
```
]
---
## Checking what was installed
- Wait a little bit ...
.exercise[
- Look at the pods in the `openebs` namespace:
```bash
kubectl get pods --namespace openebs
```
- And the StorageClasses that were created:
```bash
kubectl get sc
```
]
---
## The default StorageClasses
- OpenEBS typically creates three default StorageClasses
- `openebs-jiva-default` provisions 3 replicated Jiva pods per volume
- data is stored in `/openebs` in the replica pods
- `/openebs` is a localpath volume mapped to `/var/openebs/pvc-...` on the node
- `openebs-hostpath` uses LocalPV with local directories
- volumes are hostpath volumes created in `/var/openebs/local` on each node
- `openebs-device` uses LocalPV with local block devices
- requires available disks and/or a bit of extra configuration
- the default configuration filters out loop, LVM, MD devices
---
## When do we need custom StorageClasses?
- To store LocalPV hostpath volumes on a different path on the host
- To change the number of replicated Jiva pods
- To use a different Jiva pool
(i.e. a different path on the host to store the Jiva volumes)
- To create a cStor pool
- ...
---
class: extra-details
## Defining a custom StorageClass
Example for a LocalPV hostpath class using an extra mount on `/mnt/vol001`:
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: localpv-hostpath-mntvol001
annotations:
openebs.io/cas-type: local
cas.openebs.io/config: |
- name: BasePath
value: "/mnt/vol001"
- name: StorageType
value: "hostpath"
provisioner: openebs.io/local
```
- `provisioner` needs to be set accordingly
- Storage engine is chosen by specifying the annotation `openebs.io/cas-type`
- Storage engine configuration is set with the annotation `cas.openebs.io/config`
---
## Checking the default hostpath StorageClass
- Let's inspect the StorageClass that OpenEBS created for us
.exercise[
- Let's look at the OpenEBS LocalPV hostpath StorageClass:
```bash
kubectl get storageclass openebs-hostpath -o yaml
```
]
---
## Create a host path PVC
- Let's create a Persistent Volume Claim using an explicit StorageClass
.exercise[
```bash
kubectl apply -f - <<EOF
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-hostpath-pvc
spec:
storageClassName: openebs-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1G
EOF
```
]
---
## Making sure that a PV was created for our PVC
- Normally, the `openebs-hostpath` StorageClass created a PV for our PVC
.exercise[
- Look at the PV and PVC:
```bash
kubectl get pv,pvc
```
]
---
## Create a Pod to consume the PV
.exercise[
- Create a Pod using that PVC:
```bash
kubectl apply -f ~/container.training/k8s/openebs-pod.yaml
```
- Here are the sections that declare and use the volume:
```yaml
volumes:
- name: my-storage
persistentVolumeClaim:
claimName: local-hostpath-pvc
containers:
...
volumeMounts:
- mountPath: /mnt/storage
name: my-storage
```
]
---
## Verify that data is written on the node
- Let's find the file written by the Pod on the node where the Pod is running
.exercise[
- Get the worker node where the pod is located
```bash
kubectl get pod openebs-local-hostpath-pod -ojsonpath={.spec.nodeName}
```
- SSH into the node
- Check the volume content
```bash
sudo tail /var/openebs/local/pvc-*/greet.txt
```
]
---
## Heads up!
- The following labs and exercises will use the Jiva storage class
- This storage class creates 3 replicas by default
- It uses *anti-affinity* placement constraits to put these replicas on different nodes
- **This requires a cluster with multiple nodes!**
- It also requires the iSCSI client (aka *initiator*) to be installed on the nodes
- On many platforms, the iSCSI client is preinstalled and will start automatically
- If it doesn't, you might want to check [this documentation page] for details
[this documentation page]: https://docs.openebs.io/docs/next/prerequisites.html
---
## The default StorageClass
- The PVC that we defined earlier specified an explicit StorageClass
- We can also set a default StorageClass
- It will then be used for all PVC that *don't* specify and explicit StorageClass
- This is done with the annotation `storageclass.kubernetes.io/is-default-class`
.exercise[
- Check if we have a default StorageClass:
```bash
kubectl get storageclasses
```
]
- The default StorageClass (if there is one) is shown with `(default)`
---
## Setting a default StorageClass
- Let's set the default StorageClass to use `openebs-jiva-default`
.exercise[
- Remove the annotation (just in case we already have a default class):
```bash
kubectl annotate storageclass storageclass.kubernetes.io/is-default-class- --all
```
- Annotate the Jiva StorageClass:
```bash
kubectl annotate storageclasses \
openebs-jiva-default storageclass.kubernetes.io/is-default-class=true
```
- Check the result:
```bash
kuectl get storageclasses
```
]
---
## Creating a Pod using the Jiva class
- We will create a Pod running PostgreSQL, using the default class
.exercise[
- Create the Pod:
```bash
kubectl apply -f ~/container.training/k8s/postgres.yaml
```
- Wait for the PV, PVC, and Pod to be up:
```bash
watch kubectl get pv,pvc,pod
```
- We can also check what's going on in the `openebs` namespace:
```bash
watch kubectl get pods --namespace openebs
```
]
---
## Node failover
⚠️ This will partially break your cluster!
- We are going to disconnect the node running PostgreSQL from the cluster
- We will see what happens, and how to recover
- We will not reconnect the node to the cluster
- This whole lab will take at least 10-15 minutes (due to various timeouts)
⚠️ Only do this lab at the very end, when you don't want to run anything else after!
---
## Disconnecting the node from the cluster
.exercise[
- Find out where the Pod is running, and SSH into that node:
```bash
kubectl get pod postgres-0 -o jsonpath={.spec.nodeName}
ssh nodeX
```
- Check the name of the network interface:
```bash
sudo ip route ls default
```
- The output should look like this:
```
default via 10.10.0.1 `dev ensX` proto dhcp src 10.10.0.13 metric 100
```
- Shutdown the network interface:
```bash
sudo ip link set ensX down
```
]
---
## Watch what's going on
- Let's look at the status of Nodes, Pods, and Events
.exercise[
- In a first pane/tab/window, check Nodes and Pods:
```bash
watch kubectl get nodes,pods -o wide
```
- In another pane/tab/window, check Events:
```bash
kubectl get events --watch
```
]
---
## Node Ready → NotReady
- After \~30 seconds, the control plane stops receiving heartbeats from the Node
- The Node is marked NotReady
- It is not *schedulable* anymore
(the scheduler won't place new pods there, except some special cases)
- All Pods on that Node are also *not ready*
(they get removed from service Endpoints)
- ... But nothing else happens for now
(the control plane is waiting: maybe the Node will come back shortly?)
---
## Pod eviction
- After \~5 minutes, the control plane will evict most Pods from the Node
- These Pods are now `Terminating`
- The Pods controlled by e.g. ReplicaSets are automatically moved
(or rather: new Pods are created to replace them)
- But nothing happens to the Pods controlled by StatefulSets at this point
(they remain `Terminating` forever)
- Why? 🤔
--
- This is to avoid *split brain scenarios*
---
class: extra-details
## Split brain 🧠⚡️🧠
- Imagine that we create a replacement pod `postgres-0` on another Node
- And 15 minutes later, the Node is reconnected and the original `postgres-0` comes back
- Which one is the "right" one?
- What if they have conflicting data?
😱
- We *cannot* let that happen!
- Kubernetes won't do it
- ... Unless we tell it to
---
## The Node is gone
- One thing we can do, is tell Kubernetes "the Node won't come back"
(there are other methods; but this one is the simplest one here)
- This is done with a simple `kubectl delete node`
.exercise[
- `kubectl delete` the Node that we disconnected
]
---
## Pod rescheduling
- Kubernetes removes the Node
- After a brief period of time (\~1 minute) the "Terminating" Pods are removed
- A replacement Pod is created on another Node
- ... But it doens't start yet!
- Why? 🤔
---
## Multiple attachment
- By default, a disk can only be attached to one Node at a time
(sometimes it's a hardware or API limitation; sometimes enforced in software)
- In our Events, we should see `FailedAttachVolume` and `FailedMount` messages
- After \~5 more minutes, the disk will be force-detached from the old Node
- ... Which will allow attaching it to the new Node!
🎉
- The Pod will then be able to start
- Failover is complete!

View File

@@ -40,112 +40,6 @@
---
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

View File

@@ -1,289 +0,0 @@
# Managing secrets
- Sometimes our code needs sensitive information:
- passwords
- API tokens
- TLS keys
- ...
- *Secrets* can be used for that purpose
- Secrets and ConfigMaps are very similar
---
## Similarities between ConfigMap and Secrets
- ConfigMap and Secrets are key-value maps
(a Secret can contain zero, one, or many key-value pairs)
- They can both be exposed with the downward API or volumes
- They can both be created with YAML or with a CLI command
(`kubectl create configmap` / `kubectl create secret`)
---
## ConfigMap and Secrets are different resources
- They can have different RBAC permissions
(e.g. the default `view` role can read ConfigMaps but not Secrets)
- They indicate a different *intent*:
*"You should use secrets for things which are actually secret like API keys,
credentials, etc., and use config map for not-secret configuration data."*
*"In the future there will likely be some differentiators for secrets like rotation or support for backing the secret API w/ HSMs, etc."*
(Source: [the author of both features](https://stackoverflow.com/a/36925553/580281
))
---
## Secrets have an optional *type*
- The type indicates which keys must exist in the secrets, for instance:
`kubernetes.io/tls` requires `tls.crt` and `tls.key`
`kubernetes.io/basic-auth` requires `username` and `password`
`kubernetes.io/ssh-auth` requires `ssh-privatekey`
`kubernetes.io/dockerconfigjson` requires `.dockerconfigjson`
`kubernetes.io/service-account-token` requires `token`, `namespace`, `ca.crt`
(the whole list is in [the documentation](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types))
- This is merely for our (human) convenience:
“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
'
```
]
---
## Secrets are displayed with base64 encoding
- When shown with e.g. `kubectl get secrets -o yaml`, secrets are base64-encoded
- Likewise, when defining it with YAML, `data` values are base64-encoded
- Example:
```yaml
kind: Secret
apiVersion: v1
metadata:
name: pin-codes
data:
onetwothreefour: MTIzNA==
zerozerozerozero: MDAwMA==
```
- Keep in mind that this is just *encoding*, not *encryption*
- It is very easy to [automatically extract and decode secrets](https://medium.com/@mveritym/decoding-kubernetes-secrets-60deed7a96a3)
---
class: extra-details
## Using `stringData`
- When creating a Secret, it is possible to bypass base64
- Just use `stringData` instead of `data`:
```yaml
kind: Secret
apiVersion: v1
metadata:
name: pin-codes
stringData:
onetwothreefour: 1234
zerozerozerozero: 0000
```
- It will show up as base64 if you `kubectl get -o yaml`
- No `type` was specified, so it defaults to `Opaque`
---
class: extra-details
## Encryption at rest
- It is possible to [encrypted secrets at rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
- This means that secrets will be safe if someone ...
- steals our etcd servers
- steals our backups
- snoops the e.g. iSCSI link between our etcd servers and SAN
- However, starting the API server will now require human intervention
(to provide the decryption keys)
- This is only for extremely regulated environments (military, nation states...)
---
class: extra-details
## Immutable ConfigMaps and Secrets
- Since Kubernetes 1.19, it is possible to mark a ConfigMap or Secret as *immutable*
```bash
kubectl patch configmap xyz --patch='{"immutable": true}'
```
- This brings performance improvements when using lots of ConfigMaps and Secrets
(lots = tens of thousands)
- Once a ConfigMap or Secret has been marked as immutable:
- its content cannot be changed anymore
- the `immutable` field can't be changed back either
- the only way to change it is to delete and re-create it
- Pods using it will have to be re-created as well
???
:EN:- Handling passwords and tokens safely
:FR:- Manipulation de mots de passe, clés API etc.

View File

@@ -63,7 +63,6 @@
```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)

View File

@@ -94,20 +94,28 @@
## Building on the fly
- Conceptually, it is possible to build images on the fly from a repository
- Some services can build images on the fly from a repository
- Example: [ctr.run](https://ctr.run/)
(deprecated in August 2020, after being aquired by Datadog)
.exercise[
- It did allow something like this:
- Use ctr.run to automatically build a container image and run it:
```bash
docker run ctr.run/github.com/jpetazzo/container.training/dockercoins/hasher
```
- No alternative yet
<!--
```longwait Sinatra```
```key ^C```
-->
(free startup idea, anyone?)
]
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/).
???

View File

@@ -331,8 +331,11 @@ consul agent -data-dir=/consul/data -client=0.0.0.0 -server -ui \
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: consul
matchExpressions:
- key: app
operator: In
values:
- consul
topologyKey: kubernetes.io/hostname
```
@@ -350,7 +353,10 @@ consul agent -data-dir=/consul/data -client=0.0.0.0 -server -ui \
lifecycle:
preStop:
exec:
command: [ "sh", "-c", "consul leave" ]
command:
- /bin/sh
- -c
- consul leave
```
---

View File

@@ -1,302 +0,0 @@
# 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.
<br/>
(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).
<br/>
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

80
slides/kube-adv.yml Normal file
View File

@@ -0,0 +1,80 @@
title: |
Advanced
Kubernetes
chat: "`#kubernetes-training-november-30-december-4`"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
gitrepo: github.com/jpetazzo/container.training
slides: https://2020-11-nr.container.training/
#slidenumberprefix: "#SomeHashTag &mdash; "
exclude:
- self-paced
content:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
#- shared/chat-room-im.md
#- shared/chat-room-zoom-meeting.md
#- shared/chat-room-zoom-webinar.md
- shared/toc.md
- #1
- k8s/prereqs-admin.md
- k8s/architecture.md
- k8s/deploymentslideshow.md
- k8s/dmuc.md
- #2
- k8s/multinode.md
- k8s/cni.md
- k8s/interco.md
- #3
- k8s/cni-internals.md
- k8s/apilb.md
- k8s/control-plane-auth.md
- |
# (Extra content)
- k8s/staticpods.md
- k8s/cluster-upgrade.md
- #4
- k8s/kustomize.md
- k8s/helm-intro.md
- k8s/helm-chart-format.md
- k8s/helm-create-basic-chart.md
- |
# (Extra content)
- k8s/helm-create-better-chart.md
- k8s/helm-secrets.md
- #5
- k8s/extending-api.md
- k8s/operators.md
- k8s/sealed-secrets.md
- k8s/crd.md
- #6
- k8s/ingress-tls.md
- k8s/cert-manager.md
- k8s/eck.md
- #7
- k8s/admission.md
- k8s/kyverno.md
- #8
- k8s/aggregation-layer.md
- k8s/metrics-server.md
- k8s/prometheus.md
- k8s/hpa-v2.md
- #9
- k8s/operators-design.md
- k8s/kubebuilder.md
- k8s/events.md
- k8s/finalizers.md
- |
# (Extra content)
- k8s/owners-and-dependents.md
- k8s/apiserver-deepdive.md
#- k8s/record.md
- shared/thankyou.md

116
slides/kube-fullday.yml Normal file
View File

@@ -0,0 +1,116 @@
title: |
Deploying and Scaling Microservices
with Kubernetes
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
chat: "In person!"
gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
#slidenumberprefix: "#SomeHashTag &mdash; "
exclude:
- self-paced
content:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/chat-room-im.md
#- shared/chat-room-zoom-meeting.md
#- shared/chat-room-zoom-webinar.md
- shared/toc.md
-
- shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
#- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.md
- shared/composedown.md
- k8s/concepts-k8s.md
- k8s/kubectlget.md
-
- k8s/kubectl-run.md
- k8s/batch-jobs.md
- k8s/labels-annotations.md
- k8s/kubectl-logs.md
- k8s/logs-cli.md
- shared/declarative.md
- k8s/declarative.md
- k8s/deploymentslideshow.md
- k8s/kubenet.md
- k8s/kubectlexpose.md
- k8s/shippingimages.md
#- k8s/buildshiprun-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
#- k8s/exercise-wordsmith.md
-
- k8s/yamldeploy.md
- k8s/setup-overview.md
#- k8s/setup-devel.md
#- k8s/setup-managed.md
#- k8s/setup-selfhosted.md
#- k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
#- k8s/dryrun.md
#- k8s/exercise-yaml.md
#- k8s/localkubeconfig.md
#- k8s/accessinternal.md
#- k8s/kubectlproxy.md
- k8s/rollout.md
#- k8s/healthchecks.md
#- k8s/healthchecks-more.md
#- k8s/record.md
-
- k8s/namespaces.md
- k8s/ingress.md
#- k8s/ingress-tls.md
#- k8s/kustomize.md
#- k8s/helm-intro.md
#- k8s/helm-chart-format.md
#- k8s/helm-create-basic-chart.md
#- k8s/helm-create-better-chart.md
#- k8s/helm-secrets.md
#- k8s/exercise-helm.md
#- k8s/create-chart.md
#- k8s/create-more-charts.md
#- k8s/netpol.md
#- k8s/authn-authz.md
#- k8s/user-cert.md
#- k8s/csr-api.md
#- k8s/openid-connect.md
#- k8s/podsecuritypolicy.md
- k8s/volumes.md
#- k8s/exercise-configmap.md
#- k8s/build-with-docker.md
#- k8s/build-with-kaniko.md
- k8s/configuration.md
#- k8s/logs-centralized.md
#- k8s/prometheus.md
#- k8s/statefulsets.md
#- k8s/local-persistent-volumes.md
#- k8s/portworx.md
#- k8s/extending-api.md
#- k8s/crd.md
#- k8s/admission.md
#- k8s/operators.md
#- k8s/operators-design.md
#- k8s/staticpods.md
#- k8s/finalizers.md
#- k8s/owners-and-dependents.md
#- k8s/gitworkflows.md
-
- k8s/whatsnext.md
- k8s/lastwords.md
- k8s/links.md
- shared/thankyou.md

82
slides/kube-halfday.yml Normal file
View File

@@ -0,0 +1,82 @@
title: |
Kubernetes 101
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/training-20180413-paris)"
chat: "In person!"
gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
#slidenumberprefix: "#SomeHashTag &mdash; "
exclude:
- self-paced
content:
- shared/title.md
#- logistics.md
# Bridget-specific; others use logistics.md
- logistics-bridget.md
- k8s/intro.md
- shared/about-slides.md
- shared/chat-room-im.md
#- shared/chat-room-zoom-meeting.md
#- shared/chat-room-zoom-webinar.md
- shared/toc.md
- - shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
# Bridget doesn't go into as much depth with compose
#- shared/composescale.md
#- shared/hastyconclusions.md
- shared/composedown.md
- k8s/concepts-k8s.md
- shared/declarative.md
- k8s/declarative.md
- k8s/kubenet.md
- k8s/kubectlget.md
- k8s/setup-overview.md
#- k8s/setup-devel.md
#- k8s/setup-managed.md
#- k8s/setup-selfhosted.md
- - k8s/kubectl-run.md
#- k8s/batch-jobs.md
#- k8s/labels-annotations.md
- k8s/kubectl-logs.md
- k8s/deploymentslideshow.md
- k8s/kubectlexpose.md
- k8s/shippingimages.md
#- k8s/buildshiprun-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
#- k8s/localkubeconfig.md
#- k8s/accessinternal.md
#- k8s/kubectlproxy.md
- - k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- k8s/rollout.md
#- k8s/record.md
- - k8s/logs-cli.md
# Bridget hasn't added EFK yet
#- k8s/logs-centralized.md
- k8s/namespaces.md
- k8s/helm-intro.md
#- k8s/helm-chart-format.md
- k8s/helm-create-basic-chart.md
#- k8s/helm-create-better-chart.md
#- k8s/helm-secrets.md
#- k8s/kustomize.md
#- k8s/netpol.md
- k8s/whatsnext.md
# - k8s/links.md
# Bridget-specific
- k8s/links-bridget.md
- shared/thankyou.md

145
slides/kube-selfpaced.yml Normal file
View File

@@ -0,0 +1,145 @@
title: |
Deploying and Scaling Microservices
with Docker and Kubernetes
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
#slidenumberprefix: "#SomeHashTag &mdash; "
exclude:
- in-person
content:
- shared/title.md
#- logistics.md
- k8s/intro.md
- shared/about-slides.md
#- shared/chat-room-im.md
#- shared/chat-room-zoom-meeting.md
#- shared/chat-room-zoom-webinar.md
- shared/toc.md
-
- shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.md
- shared/composedown.md
- k8s/concepts-k8s.md
-
- k8s/kubectlget.md
- k8s/kubectl-run.md
- k8s/batch-jobs.md
- k8s/labels-annotations.md
- k8s/kubectl-logs.md
- k8s/logs-cli.md
- shared/declarative.md
- k8s/declarative.md
- k8s/deploymentslideshow.md
-
- k8s/kubenet.md
- k8s/kubectlexpose.md
- k8s/shippingimages.md
- k8s/buildshiprun-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
#- k8s/exercise-wordsmith.md
- k8s/yamldeploy.md
-
- k8s/setup-overview.md
- k8s/setup-devel.md
- k8s/setup-managed.md
- k8s/setup-selfhosted.md
- k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- k8s/dryrun.md
#- k8s/exercise-yaml.md
-
- k8s/rollout.md
- k8s/healthchecks.md
- k8s/healthchecks-more.md
- k8s/record.md
-
- k8s/namespaces.md
- k8s/localkubeconfig.md
- k8s/accessinternal.md
- k8s/kubectlproxy.md
-
- k8s/ingress.md
- k8s/ingress-tls.md
- k8s/cert-manager.md
- k8s/kustomize.md
- k8s/helm-intro.md
- k8s/helm-chart-format.md
- k8s/helm-create-basic-chart.md
- k8s/helm-create-better-chart.md
- k8s/helm-secrets.md
#- k8s/exercise-helm.md
-
- k8s/netpol.md
- k8s/authn-authz.md
- k8s/podsecuritypolicy.md
- k8s/user-cert.md
- k8s/csr-api.md
- k8s/openid-connect.md
- k8s/control-plane-auth.md
-
- k8s/volumes.md
#- k8s/exercise-configmap.md
- k8s/build-with-docker.md
- k8s/build-with-kaniko.md
-
- k8s/configuration.md
- k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md
-
- k8s/logs-centralized.md
- k8s/prometheus.md
- k8s/resource-limits.md
- k8s/metrics-server.md
- k8s/cluster-sizing.md
- k8s/horizontal-pod-autoscaler.md
- k8s/hpa-v2.md
-
- k8s/extending-api.md
- k8s/apiserver-deepdive.md
- k8s/crd.md
- k8s/aggregation-layer.md
- k8s/admission.md
- k8s/operators.md
- k8s/operators-design.md
- k8s/kubebuilder.md
- k8s/sealed-secrets.md
- k8s/kyverno.md
- k8s/eck.md
- k8s/finalizers.md
- k8s/owners-and-dependents.md
- k8s/events.md
-
- k8s/dmuc.md
- k8s/multinode.md
- k8s/cni.md
- k8s/cni-internals.md
- k8s/apilb.md
- k8s/staticpods.md
-
- k8s/cluster-upgrade.md
- k8s/cluster-backup.md
- k8s/cloud-controller-manager.md
- k8s/gitworkflows.md
-
- k8s/lastwords.md
- k8s/links.md
- shared/thankyou.md

115
slides/kube-twodays.yml Normal file
View File

@@ -0,0 +1,115 @@
title: |
Deploying and Scaling Microservices
with Kubernetes
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
chat: "In person!"
gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
#slidenumberprefix: "#SomeHashTag &mdash; "
exclude:
- self-paced
content:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/chat-room-im.md
#- shared/chat-room-zoom-meeting.md
#- shared/chat-room-zoom-webinar.md
- shared/toc.md
-
- shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
#- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.md
- shared/composedown.md
- k8s/concepts-k8s.md
- k8s/kubectlget.md
-
- k8s/kubectl-run.md
- k8s/batch-jobs.md
- k8s/labels-annotations.md
- k8s/kubectl-logs.md
- k8s/logs-cli.md
- shared/declarative.md
- k8s/declarative.md
- k8s/deploymentslideshow.md
- k8s/kubenet.md
- k8s/kubectlexpose.md
- k8s/shippingimages.md
#- k8s/buildshiprun-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
#- k8s/exercise-wordsmith.md
-
- k8s/yamldeploy.md
- k8s/setup-overview.md
- k8s/setup-devel.md
#- k8s/setup-managed.md
#- k8s/setup-selfhosted.md
- k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- k8s/dryrun.md
#- k8s/exercise-yaml.md
-
- k8s/localkubeconfig.md
- k8s/accessinternal.md
#- k8s/kubectlproxy.md
- k8s/rollout.md
- k8s/healthchecks.md
#- k8s/healthchecks-more.md
- k8s/record.md
-
- k8s/namespaces.md
- k8s/ingress.md
#- k8s/ingress-tls.md
- k8s/kustomize.md
- k8s/helm-intro.md
- k8s/helm-chart-format.md
- k8s/helm-create-basic-chart.md
- k8s/helm-create-better-chart.md
- k8s/helm-secrets.md
#- k8s/exercise-helm.md
-
- k8s/netpol.md
- k8s/authn-authz.md
#- k8s/csr-api.md
#- k8s/openid-connect.md
#- k8s/podsecuritypolicy.md
-
- k8s/volumes.md
#- k8s/exercise-configmap.md
#- k8s/build-with-docker.md
#- k8s/build-with-kaniko.md
- k8s/configuration.md
- k8s/logs-centralized.md
- k8s/prometheus.md
-
- k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md
#- k8s/extending-api.md
#- k8s/admission.md
#- k8s/operators.md
#- k8s/operators-design.md
#- k8s/staticpods.md
#- k8s/owners-and-dependents.md
#- k8s/gitworkflows.md
-
- k8s/whatsnext.md
- k8s/lastwords.md
- k8s/links.md
- shared/thankyou.md

View File

@@ -1,57 +0,0 @@
title: |
Cloud Native
Continuous Deployment
with GitLab, Helm, and
Linode Kubernetes Engine
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
chat: "In person!"
gitrepo: github.com/jpetazzo/container.training
slides: https://2021-03-lke.container.training/
#slidenumberprefix: "#SomeHashTag &mdash; "
exclude:
- self-paced
content:
- shared/title.md
- logistics.md
- shared/about-slides.md
#- shared/chat-room-im.md
#- shared/chat-room-zoom-meeting.md
#- shared/chat-room-zoom-webinar.md
- shared/toc.md
-
- lke/intro.md
- shared/sampleapp.md
- shared/composedown.md
- lke/deploy-cluster.md
- lke/kubernetes-review.md
- k8s/deploymentslideshow.md
- k8s/accessinternal.md
- lke/what-is-missing.md
-
- k8s/helm-intro.md
- lke/external-dns.md
- lke/traefik.md
- lke/metrics-server.md
#- k8s/prometheus.md
- lke/prometheus.md
- k8s/cert-manager.md
- k8s/gitlab.md
#- k8s/helm-chart-format.md
#- k8s/helm-create-basic-chart.md
#- k8s/helm-create-better-chart.md
#- k8s/helm-dependencies.md
#- k8s/helm-values-schema-validation.md
#- k8s/helm-secrets.md
#- k8s/exercise-helm.md
-
- shared/thankyou.md
#grep md$ lke.yml | grep -v '#' | cut -d- -f2- | xargs subl3

View File

@@ -1,163 +0,0 @@
# Deploying our LKE cluster
- *If we wanted to deploy Kubernetes manually*, what would we need to do?
(not that I recommend doing that...)
- Control plane (etcd, API server, scheduler, controllers)
- Nodes (VMs with a container engine + the Kubelet agent; CNI setup)
- High availability (etcd clustering, API load balancer)
- Security (CA and TLS certificates everywhere)
- Cloud integration (to provision LoadBalancer services, storage...)
*And that's just to get a basic cluster!*
---
## The best way to deploy Kubernetes
*The best way to deploy Kubernetes is to get someone else to
do it for us.*
(Me, ever since I've been working with Kubernetes)
---
## Managed Kubernetes
- Cloud provider runs the control plane
(including etcd, API load balancer, TLS setup, cloud integration)
- We run nodes
(the cloud provider generally gives us an easy way to provision them)
- Get started in *minutes*
- We're going to use [Linode Kubernetes Engine](https://www.linode.com/products/kubernetes/)
---
## Creating a cluster
- With the web console:
https://cloud.linode.com/kubernetes/clusters
- Pick the region of your choice
- Pick the latest available Kubernetes version
- Pick 3 nodes with 8 GB of RAM
- Click! ✨
- Wait a few minutes... ⌚️
- Download the kubeconfig file 💾
---
## With the CLI
- View available regions with `linode-cli regions list`
- View available server types with `linode-cli linodes types`
- View available Kubernetes versions with `linode-cli lke versions-list`
- Create cluster:
```bash
linode-cli lke cluster-create --label=hello-lke --region=us-east \
--k8s_version=1.20 --node_pools.type=g6-standard-4 --node_pools.count=3
```
- Note the cluster ID (e.g.: 12345)
- Download the kubeconfig file:
```bash
linode-cli lke kubeconfig-view `12345` --text --no-headers | base64 -d
```
---
## Communicating with the cluster
- All the Kubernetes tools (`kubectl`, but also `helm` etc) use the same config file
- That file is (by default) `$HOME/.kube/config`
- It can hold multiple cluster definitions (or *contexts*)
- Or, we can have multiple config files and switch between them:
- by adding the `--kubeconfig` flag each time we invoke a tool (🙄)
- or by setting the `KUBECONFIG` environment variable (☺️)
---
## Using the kubeconfig file
Option 1:
- move the kubeconfig file to e.g. `~/.kube/config.lke`
- set the environment variable: `export KUBECONFIG=~/.kube/config.lke`
Option 2:
- directly move the kubeconfig file to `~/.kube/config`
- **do not** do that if you already have a file there!
Option 3:
- merge the new kubeconfig file with our existing file
---
## Merging kubeconfig
- Assuming that we want to merge `~/.kube/config` and `~/.kube/config.lke` ...
- Move our existing kubeconfig file:
```bash
cp ~/.kube/config ~/.kube/config.old
```
- Merge both files:
```bash
KUBECONFIG=~/.kube/config.old:~/.kube/config.lke kubectl config \
view --raw > ~/.kube/config
```
- Check that everything is there:
```bash
kubectl config get-contexts
```
---
## Are we there yet?
- Let's check if our control plane is available:
```bash
kubectl get services
```
→ This should show the `kubernetes` `ClusterIP` service
- Look for our nodes:
```bash
kubectl get nodes
```
→ This should show 3 nodes (or whatever amount we picked earlier)
- If the nodes aren't visible yet, give them a minute to join the cluster

View File

@@ -1,108 +0,0 @@
# [ExternalDNS](https://github.com/kubernetes-sigs/external-dns)
- ExternalDNS will automatically create DNS records from Kubernetes resources
- Services (with the annotation `external-dns.alpha.kubernetes.io/hostname`)
- Ingresses (automatically)
- It requires a domain name (obviously)
- ... And that domain name should be configurable through an API
- As of April 2021, it supports [a few dozens of providers](https://github.com/kubernetes-sigs/external-dns#status-of-providers)
- We're going to use Linode DNS
---
## Prep work
- We need a domain name
(if you need a cheap one, look e.g. at [GANDI](https://shop.gandi.net/?search=funwithlinode); there are many options below $10)
- That domain name should be configured to point to Linode DNS servers
(ns1.linode.com to ns5.linode.com)
- We need to generate a Linode API token with DNS API access
- Pro-tip: reduce the default TTL of the domain to 5 minutes!
---
## Deploying ExternalDNS
- The ExternalDNS documentation has a [tutorial](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/linode.md) for Linode
- ... It's basically a lot of YAML!
- That's where using a Helm chart will be very helpful
- There are a few ExternalDNS charts available out there
- We will use the one from Bitnami
(these folks maintain *a lot* of great Helm charts!)
---
## How we'll install things with Helm
- We will install each chart in its own namespace
(this is not mandatory, but it helps to see what belongs to what)
- We will use `helm upgrade --install` instead of `helm install`
(that way, if we want to change something, we can just re-run the command)
- We will use the `--create-namespace` and `--namespace ...` options
- To keep things boring and predictible, if we are installing chart `xyz`:
- we will install it in namespace `xyz`
- we will name the release `xyz` as well
---
## Installing ExternalDNS
- First, let's add the Bitnami repo:
```bash
helm repo add bitnami https://charts.bitnami.com/bitnami
```
- Then, install ExternalDNS:
```bash
LINODE_API_TOKEN=`1234abcd...6789`
helm upgrade --install external-dns bitnami/external-dns \
--namespace external-dns --create-namespace \
--set provider=linode \
--set linode.apiToken=$LINODE_API_TOKEN
```
(Make sure to update your API token above!)
---
## Testing ExternalDNS
- Let's annotate our NGINX service to expose it with a DNS record:
```bash
kubectl annotate service web \
external-dns.alpha.kubernetes.io/hostname=nginx.`cloudnative.party`
```
(make sure to use *your* domain name above, otherwise that won't work!)
- Check ExternalDNS logs:
```bash
kubectl logs -n external-dns -l app.kubernetes.io/name=external-dns
```
- It might take a few minutes for ExternalDNS to start, patience!
- Then try to access `nginx.cloudnative.party` (or whatever domain you picked)

View File

@@ -1,175 +0,0 @@
# Get ready!
- We're going to set up a whole Continous Deployment pipeline
- ... for Kubernetes apps
- ... on a Kubernetes cluster
- Ingredients: cert-manager, GitLab, Helm, Linode DNS, LKE, Traefik
---
## Philosophy
- "Do one thing, do it well"
--
- ... But a CD pipeline is a complex system with interconnected parts!
- GitLab is no exception to that rule
- Let's have a look at its components!
---
## GitLab components
- GitLab dependencies listed in the GitLab official Helm chart
- External dependencies:
cert-manager, grafana, minio, nginx-ingress, postgresql, prometheus,
redis, registry, shared-secrets
(these dependencies correspond to external charts not created by GitLab)
- Internal dependencies:
geo-logcursor, gitaly, gitlab-exporter, gitlab-grafana, gitlab-pages,
gitlab-shell, kas, mailroom, migrations, operator, praefect, sidekiq,
task-runner, webservice
(these dependencies correspond to subcharts embedded in the GitLab chart)
---
## Philosophy
- Use the GitLab chart to deploy everything that is specific to GitLab
- Deploy cluster-wide components separately
(cert-manager, ExternalDNS, Ingress Controller...)
---
## What we're going to do
- Spin up an LKE cluster
- Run a simple test app
- Install a few extras
(the cluster-wide components mentioned earlier)
- Set up GitLab
- Push an app with a CD pipeline to GitLab
---
## What you need to know
- If you just want to follow along and watch...
- container basics (what's an image, what's a container...)
- Kubernetes basics (what are Deployments, Namespaces, Pods, Services)
- If you want to run this on your own Kubernetes cluster...
- intermediate Kubernetes concepts (annotations, Ingresses)
- Helm basic concepts (how to install/upgrade releases; how to set "values")
- basic Kubernetes troubleshooting commands (view logs, events)
- There will be a lot of explanations and reminders along the way
---
## What you need to have
If you want to run this on your own...
- A Linode account
- A domain name that you will point to Linode DNS
(I got cloudnative.party for $5)
- Local tools to control your Kubernetes cluster:
- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
- [helm](https://helm.sh/docs/intro/install/)
- Patience, as many operations will require us to wait a few minutes!
---
## Do I really need a Linode account?
- *Can I use a local cluster, e.g. with Minikube?*
It will be very difficult to get valid TLS certs with a local cluster.
Also, GitLab needs quite a bit of resources.
- *Can I use another Kubernetes provider?*
You certainly can: Kubernetes is a standard platform!
But you'll have to adjust a few things.
(I'll try my best to tell you what as we go along.)
---
## Why do I need a domain name?
- Because accessing gitlab.cloudnative.party is easier than 102.34.55.67
- Because we'll need TLS certificates
(and it's very easy to obtain certs with Let's Encrypt when we have a domain)
- We'll illustrate automatic DNS configuration with ExternalDNS, too!
(Kubernetes will automatically create DNS entries in our domain)
---
## Nice-to-haves
Here are a few tools that I like...
- [linode-cli](https://github.com/linode/linode-cli#installation)
to manage Linode resources from the command line
- [stern](https://github.com/stern/stern)
to comfortably view logs of Kubernetes pods
- [k9s](https://k9scli.io/topics/install/)
to manage Kubernetes resources with that retro BBS look and feel 😎
- [kube-ps1](https://github.com/jonmosco/kube-ps1)
to keep track of which Kubernetes cluster and namespace we're working on
- [kubectx](https://github.com/ahmetb/kubectx)
to easily switch between clusters, contexts, and namespaces
---
## Warning ⚠️💸
- We're going to spin up cloud resources
- Remember to shut them down when you're down!
- In the immortal words of Cloud Economist [Corey Quinn](https://twitter.com/QuinnyPig):
*[You're charged for what you forget to turn off.](https://www.theregister.com/2020/09/03/cloud_control_costs/)*

View File

@@ -1,168 +0,0 @@
# Quick Kubernetes review
- Let's deploy a simple HTTP server
- And expose it to the outside world!
- Feel free to skip this section if you're familiar with Kubernetes
---
## Creating a container
- On Kubernetes, one doesn't simply run a container
- We need to create a "Pod"
- A Pod will be a group of containers running together
(often, it will be a group of *one* container)
- We can create a standalone Pod, but generally, we'll use a *controller*
(for instance: Deployment, Replica Set, Daemon Set, Job, Stateful Set...)
- The *controller* will take care of scaling and recreating the Pod if needed
(note that within a Pod, containers can also be restarted automatically if needed)
---
## A *controller*, you said?
- We're going to use one of the most common controllers: a *Deployment*
- Deployments...
- can be scaled (will create the requested number of Pods)
- will recreate Pods if e.g. they get evicted or their Node is down
- handle rolling updates
- Deployments actually delegate a lot of these tasks to *Replica Sets*
- We will generally have the following hierarchy:
Deployment → Replica Set → Pod
---
## Creating a Deployment
- Without further ado:
```bash
kubectl create deployment web --image=nginx
```
- Check what happened:
```bash
kubectl get all
```
- Wait until the NGINX Pod is "Running"!
- Note: `kubectl create deployment` is great when getting started...
- ... But later, we will probably write YAML instead!
---
## Exposing the Deployment
- We need to create a Service
- We can use `kubectl expose` for that
(but, again, we will probably use YAML later!)
- For *internal* use, we can use the default Service type, ClusterIP:
```bash
kubectl expose deployment web --port=80
```
- For *external* use, we can use a Service of type LoadBalancer:
```bash
kubectl expose deployment web --port=80 --type=LoadBalancer
```
---
## Changing the Service type
- We can `kubectl delete service web` and recreate it
- Or, `kubectl edit service web` and dive into the YAML
- Or, `kubectl patch service web --patch '{"spec": {"type": "LoadBalancer"}}'`
- ... These are just a few "classic" methods; there are many ways to do this!
---
## Deployment → Pod
- Can we check exactly what's going on when the Pod is created?
- Option 1: `watch kubectl get all`
- displays all object types
- refreshes every 2 seconds
- puts a high load on the API server when there are many objects
- Option 2: `kubectl get pods --watch --output-watch-events`
- can only display one type of object
- will show all modifications happening (à la `tail -f`)
- doesn't put a high load on the API server (except for initial display)
---
## Recreating the Deployment
- Let's delete our Deployment:
```bash
kubectl delete deployment web
```
- Watch Pod updates:
```bash
kubectl get pods --watch --output-watch-events
```
- Recreate the Deployment and see what Pods do:
```bash
kubectl create deployment web --image=nginx
```
---
## Service stability
- Our Service *still works* even though we deleted and re-created the Deployment
- It wouldn't have worked while the Deployment was deleted, though
- A Service is a *stable endpoint*
???
:T: Warming up with a quick Kubernetes review
:Q: In Kubernetes, what is a Pod?
:A: ✔A basic unit of scaling that can contain one or more containers
:A: An abstraction for an application and its dependencies
:A: It's just a fancy name for "container" but they're the same
:A: A group of cluster nodes used for scheduling purposes
:Q: In Kubernetes, what is a Replica Set?
:A: ✔A controller used to create one or multiple identical Pods
:A: A numeric parameter in a Pod specification, used to scale that Pod
:A: A group of containers running on the same node
:A: A group of containers running on different nodes
:Q: In Kubernetes, what is a Deployment?
:A: ✔A controller that can manage Replica Sets corresponding to different configurations
:A: A manifest telling Kubernetes how to deploy an app and its dependencies
:A: A list of instructions executed in a container to configure that container
:A: A basic unit of work for the Kubernetes scheduler

View File

@@ -1,147 +0,0 @@
# Installing metrics-server
- We've installed a few things on our cluster so far
- How much resources (CPU, RAM) are we using?
- We need metrics!
- If metrics-server is installed, we can get Nodes metrics like this:
```bash
kubectl top nodes
```
- At the moment, this should show us `error: Metrics API not available`
- How do we fix this?
---
## Many ways to get metrics
- We could use a SAAS like Datadog, New Relic...
- We could use a self-hosted solution like Prometheus
- Or we could use metrics-server
- What's special about metrics-server?
---
## Pros/cons
Cons:
- no data retention (no history data, just instant numbers)
- only CPU and RAM of nodes and pods (no disk or network usage or I/O...)
Pros:
- very lightweight
- doesn't require storage
- used by Kubernetes autoscaling
---
## Why metrics-server
- We may install something fancier later
(think: Prometheus with Grafana)
- But metrics-server will work in *minutes*
- It will barely use resources on our cluster
- It's required for autoscaling anyway
---
## How metric-server works
- It runs a single Pod
- That Pod will fetch metrics from all our Nodes
- It will expose them through the Kubernetes API agregation layer
(we won't say much more about that agregation layer; that's fairly advanced stuff!)
---
## Installing metrics-server
- In a lot of places, this is done with a little bit of custom YAML
(derived from the [official installation instructions](https://github.com/kubernetes-sigs/metrics-server#installation))
- We're going to use Helm one more time:
```bash
helm upgrade --install metrics-server bitnami/metrics-server \
--create-namespace --namespace metrics-server \
--set apiService.create=true \
--set extraArgs.kubelet-insecure-tls=true \
--set extraArgs.kubelet-preferred-address-types=InternalIP
```
- What are these options for?
---
## Installation options
- `apiService.create=true`
register `metrics-server` with the Kubernetes agregation layer
(create an entry that will show up in `kubectl get apiservices`)
- `extraArgs.kubelet-insecure-tls=true`
when connecting to nodes to collect their metrics, don't check kubelet TLS certs
(because most kubelet certs include the node name, but not its IP address)
- `extraArgs.kubelet-preferred-address-types=InternalIP`
when connecting to nodes, use their internal IP address instead of node name
(because the latter requires an internal DNS, which is rarely configured)
---
## Testing metrics-server
- After a minute or two, metrics-server should be up
- We should now be able to check Nodes resource usage:
```bash
kubectl top nodes
```
- And Pods resource usage, too:
```bash
kubectl top pods --all-namespaces
```
---
## Keep some padding
- The RAM usage that we see should correspond more or less to the Resident Set Size
- Our pods also need some extra space for buffers, caches...
- Do not aim for 100% memory usage!
- Some more realistic targets:
50% (for workloads with disk I/O and leveraging caching)
90% (on very big nodes with mostly CPU-bound workloads)
75% (anywhere in between!)

View File

@@ -1,120 +0,0 @@
# Prometheus and Grafana
- What if we want metrics retention, view graphs, trends?
- A very popular combo is Prometheus+Grafana:
- Prometheus as the "metrics engine"
- Grafana to display comprehensive dashboards
- Prometheus also has an alert-manager component to trigger alerts
(we won't talk about that one)
---
## Installing Prometheus and Grafana
- A complete metrics stack needs at least:
- the Prometheus server (collects metrics and stores them efficiently)
- a collection of *exporters* (exposing metrics to Prometheus)
- Grafana
- a collection of Grafana dashboards (building them from scratch is tedious)
- The Helm chart `kube-prometheus-stack` combines all these elements
- ... So we're going to use it to deploy our metrics stack!
---
## Installing `kube-prometheus-stack`
- Let's install that stack *directly* from its repo
(without doing `helm repo add` first)
- Otherwise, keep the same naming strategy:
```bash
helm upgrade --install kube-prometheus-stack kube-prometheus-stack \
--namespace kube-prometheus-stack --create-namespace \
--repo https://prometheus-community.github.io/helm-charts
```
- This will take a minute...
- Then check what was installed:
```bash
kubectl get all --namespace kube-prometheus-stack
```
---
## Exposing Grafana
- Let's create an Ingress for Grafana
```bash
kubectl create ingress --namespace kube-prometheus-stack grafana \
--rule=grafana.`cloudnative.party`/*=kube-prometheus-stack-grafana:80
```
(as usual, make sure to use *your* domain name above)
- Connect to Grafana
(remember that the DNS record might take a few minutes to come up)
---
## Grafana credentials
- What could the login and password be?
- Let's look at the Secrets available in the namespace:
```bash
kubectl get secrets --namespace kube-prometheus-stack
```
- There is a `kube-prometheus-stack-grafana` that looks promising!
- Decode the Secret:
```bash
kubectl get secret --namespace kube-prometheus-stack \
kube-prometheus-stack-grafana -o json | jq '.data | map_values(@base64d)'
```
- If you don't have the `jq` tool mentioned above, don't worry...
--
- The login/password is hardcoded to `admin`/`prom-operator` 😬
---
## Grafana dashboards
- Once logged in, click on the "Dashboards" icon on the left
(it's the one that looks like four squares)
- Then click on the "Manage" entry
- Then click on "Kubernetes / Compute Resources / Cluster"
- This gives us a breakdown of resource usage by Namespace
- Feel free to explore the other dashboards!
???
:T: Observing our cluster with Prometheus and Grafana
:Q: What's the relationship between Prometheus and Grafana?
:A: Prometheus collects and graphs metrics; Grafana sends alerts
:A: ✔Prometheus collects metrics; Grafana displays them on dashboards
:A: Prometheus collects and graphs metrics; Grafana is its configuration interface
:A: Grafana collects and graphs metrics; Prometheus sends alerts

View File

@@ -1,150 +0,0 @@
# Installing Traefik
- Traefik is going to be our Ingress Controller
- Let's install it with a Helm chart, in its own namespace
- First, let's add the Traefik chart repository:
```bash
helm repo add traefik https://helm.traefik.io/traefik
```
- Then, install the chart:
```bash
helm upgrade --install traefik traefik/traefik \
--create-namespace --namespace traefik \
--set "ports.websecure.tls.enabled=true"
```
(that option that we added enables HTTPS, it will be useful later!)
---
## Testing Traefik
- Let's create an Ingress resource!
- If we're using Kubernetes 1.20 or later, we can simply do this:
```bash
kubectl create ingress web \
--rule=`ingress-is-fun.cloudnative.party`/*=web:80
```
(make sure to update and use your own domain)
- Check that the Ingress was correctly created:
```bash
kubectl get ingress
kubectl describe ingress
```
- If we're using Kubernetes 1.19 or earlier, we'll need some YAML
---
## Creating an Ingress with YAML
- This is how we do it with YAML:
```bash
kubectl apply -f- <<EOF
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: web
spec:
rules:
- host: `ingress-is-fun.cloudnative.party`
http:
paths:
- path: /
backend:
serviceName: web
servicePort: 80
EOF
```
---
## Ingress versions...
- Note how we used the `v1beta1` Ingress version on the previous YAML
(to be compatible with older Kubernetes versions)
- This YAML will give you deprecation warnings on recent version of Kubernetes
(since the Ingress spec is now at version `v1`)
- Don't worry too much about the deprecation warnings
(on Kubernetes, deprecation happens over a long time window, typically 1 year)
- You will have time to revisit and worry later! 😅
---
## Does it work?
- Try to connect to the Ingress host name
(in my example, http://ingress-is-fun.cloudnative.party/)
- *Normally,* it doesn't work (yet) 🤔
- Let's look at `kubectl get ingress` again
- ExternalDNS is trying to create records mapping HOSTS to ADDRESS
- But the ADDRESS field is currently empty!
- We need to tell Traefik to fill that ADDRESS field
---
## Reconfiguring Traefik
- There is a "magic" flag to tell Traefik to update the address status field
- Let's update our Traefik install:
```bash
helm upgrade --install traefik traefik/traefik \
--create-namespace --namespace traefik \
--set "ports.websecure.tls.enabled=true" \
--set "providers.kubernetesIngress.publishedService.enabled=true"
```
---
## Checking what we did
- Check the output of `kubectl get ingress`
(there should be an address now)
- Check the logs of ExternalDNS
(there should be a mention of the new DNS record)
- Try again to connect to the HTTP address
(now it should work)
- Note that some of these operations might take a minute or two
(be patient!)
???
:T: Installing the Traefik Ingress Controller
:Q: What's the job of an Ingress Controller?
:A: Prevent unauthorized access to Kubernetes services
:A: Firewall inbound traffic on the Kubernetes API
:A: ✔Handle inbound HTTP traffic for Kubernetes services
:A: Keep track of the location of Kubernetes operators
:Q: What happens when we create an "Ingress resource"?
:A: A web service is automatically deployed and scaled on our cluster
:A: Kubernetes starts tracking the location of our users
:A: Traffic coming from the specified addresses will be allowed
:A: ✔A load balancer is configured with HTTP traffic rules

View File

@@ -1,87 +0,0 @@
# DNS, Ingress, Metrics
- We got a basic app up and running
- We accessed it over a raw IP address
- Can we do better?
(i.e. access it with a domain name!)
- How much resources is it using?
---
## DNS
- We'd like to associate a fancy name to that LoadBalancer Service
(e.g. `nginx.cloudnative.party``A.B.C.D`)
- option 1: manually add a DNS record
- option 2: find a way to create DNS records automatically
- We will install ExternalDNS to automate DNS records creatoin
- ExternalDNS supports Linode DNS and dozens of other providers
---
## Ingress
- What if we have multiple web services to expose?
- We could create one LoadBalancer Service for each of them
- This would create a lot of cloud load balancers
(and they typically incur a cost, even if it's a small one)
- Instead, we can use an *Ingress Controller*
- Ingress Controller = HTTP load balancer / reverse proxy
- Put all our HTTP services behind a single LoadBalancer Service
- Can also do fancy "content-based" routing (using headers, request path...)
- We will install Traefik as our Ingress Controller
---
## Metrics
- How much resources are we using right now?
- When will we need to scale up our cluster?
- We need metrics!
- We're going to install the *metrics server*
- It's a very basic metrics system
(no retention, no graphs, no alerting...)
- But it's lightweight, and it is used internally by Kubernetes for autoscaling
---
## What's next
- We're going to install all these components
- Very often, things can be installed with a simple YAML file
- Very often, that YAML file needs to be customized a little bit
(add command-line parameters, provide API tokens...)
- Instead, we're going to use Helm charts
- Helm charts give us a way to customize what we deploy
- Helm can also keep track of what we install
(for easier uninstall and updates)

View File

@@ -1,15 +1,59 @@
## Intros
## Intros & disclaimers
- Hello! I'm Jérôme Petazzoni
- Hello! I'm Jérôme Petazzoni ([@jpetazzo](https://twitter.com/jpetazzo))
([@jpetazzo](https://twitter.com/jpetazzo) on Twitter)
- I have ...
- I worked at Docker from \~2011 to 2018
- extensive experience running *containers* in production
- limited experience running *Kubernetes* in production
- I'm now doing consulting, training, etc. on Docker & Kubernetes
- taught Docker and Kubernetes many times, to large audiences
- less frequently taught operators and API internals
(check out [container.training](https://container.training/)!)
- written a lot of Python code during my career; but much less Go
- I'll show you how to deploy a complete CI/CD pipeline on LKE!
- learned way more than I expected just by writing some chapters of this course (!)
(Linode Kubernetes Engine 😎)
---
## Logistics
- The training will from 8am to noon PST, Monday to Friday
- There will be short breaks every hour, and a longer break in the middle
- Feel free to interrupt for questions at any time
- *Especially when you see full screen container pictures!*
(I will watch them in silence while I wait for your questions)
- Live feedback, questions, help: @@CHAT@@
---
## Course structure
Three axis:
1. Fundamental concepts, "under the hood" components
(think: data structures, algorithms, assembly language ...)
2. Analyzing state-of-the-art implementations
(think: design patterns ...)
3. High-level abstractions, highly effective frameworks
(think: Django, Ruby ...)
---
## The power of repetition ...
- You will notice some repetition in the beginning of some chapters
- This is to let you review some chapters in isolation, just in case
- Also, supposedly, repetition yields better learning results? 🤷🏻

View File

@@ -213,7 +213,6 @@ 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)

View File

@@ -1,12 +0,0 @@
## Chat room
- A Slack room has been set up for the duration of the training
- We'll use it to ask questions, get help, share feedback ...
(let's keep an eye on it during the training!)
- Reminder, the room is @@CHAT@@
- Say hi in the chat room!

View File

@@ -1,8 +1,8 @@
# Our sample application
- I'm going to run our demo app locally, with Docker
- We will clone the GitHub repository onto our `node1`
(you don't have to do that; do it if you like!)
- The repository also contains scripts and tools that we will use through the workshop
.exercise[
@@ -15,7 +15,7 @@ fi
```
-->
- Clone the repository:
- Clone the repository on `node1`:
```bash
git clone https://@@GITREPO@@
```
@@ -34,7 +34,7 @@ Let's start this before we look around, as downloading will take a little time..
- Go to the `dockercoins` directory, in the cloned repo:
```bash
cd container.training/dockercoins
cd ~/container.training/dockercoins
```
- Use Compose to build and run all containers:

View File

@@ -6,6 +6,6 @@ Thank you!
class: title, in-person
That's all, folks! <br/> Thank you ✨
That's all, folks! <br/> Questions?
![end](images/end.jpg)

View File

@@ -29,7 +29,7 @@ code {
code.remark-code {
font-size: 100%;
}
.x-exercise ul li code.remark-code.hljs.bash {
.exercise ul li code.remark-code.hljs.bash {
padding: 0;
}
@@ -109,17 +109,8 @@ div.pic p {
div.pic img {
display: block;
margin: auto;
/*
"pic" class slides should have a single, full screen picture.
We used to have these attributes below but they prevented
pictures from taking up the whole slide. Replacing them with
100%/100% seems to put the pictures full screen, but I've left
these old attributes here just in case.
max-width: 1210px;
max-height: 550px;
*/
max-width: 100%;
max-height: 100%;
}
div.pic h1, div.pic h2, div.title h1, div.title h2 {
text-align: center;
@@ -177,7 +168,7 @@ div img {
background-repeat: no-repeat;
background-position: left;
}
.x-exercise {
.exercise {
background-color: #eee;
background-image: url("images/keyboard.png");
background-size: 1.4em;
@@ -185,7 +176,7 @@ div img {
background-position: 0.2em 0.2em;
border: 2px dotted black;
}
.x-exercise:before {
.exercise:before {
content: "Exercise";
margin-left: 1.8em;
}