Compare commits
8 Commits
2021-06-mb
...
2020-11-nr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3797fc6f9f | ||
|
|
242a3d9ddf | ||
|
|
a14f5e81ce | ||
|
|
109f6503e4 | ||
|
|
337be57182 | ||
|
|
63d88236b2 | ||
|
|
799aa21302 | ||
|
|
95247d6d39 |
@@ -1,6 +1,3 @@
|
||||
# Note: hyperkube isn't available after Kubernetes 1.18.
|
||||
# So we'll have to update this for Kubernetes 1.19!
|
||||
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ metadata:
|
||||
name: fluentd
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: fluentd
|
||||
@@ -21,7 +21,7 @@ rules:
|
||||
- watch
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: fluentd
|
||||
roleRef:
|
||||
|
||||
@@ -11,7 +11,7 @@ metadata:
|
||||
name: elasticsearch-operator
|
||||
namespace: elasticsearch-operator
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: elasticsearch-operator
|
||||
@@ -41,7 +41,7 @@ rules:
|
||||
resources: ["elasticsearchclusters"]
|
||||
verbs: ["*"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: elasticsearch-operator
|
||||
@@ -55,16 +55,13 @@ subjects:
|
||||
name: elasticsearch-operator
|
||||
namespace: elasticsearch-operator
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: elasticsearch-operator
|
||||
namespace: elasticsearch-operator
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
name: elasticsearch-operator
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
@@ -131,7 +131,7 @@ spec:
|
||||
path: /var/lib/filebeat-data
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: filebeat
|
||||
@@ -144,7 +144,7 @@ roleRef:
|
||||
name: filebeat
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: filebeat
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kubernetes-dashboard
|
||||
@@ -11,4 +11,4 @@ roleRef:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
namespace: kube-system
|
||||
@@ -1,34 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: hackthecluster
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hackthecluster
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hackthecluster
|
||||
spec:
|
||||
volumes:
|
||||
- name: slash
|
||||
hostPath:
|
||||
path: /
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
containers:
|
||||
- name: alpine
|
||||
image: alpine
|
||||
volumeMounts:
|
||||
- name: slash
|
||||
mountPath: /hostfs
|
||||
command:
|
||||
- sleep
|
||||
- infinity
|
||||
securityContext:
|
||||
#privileged: true
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
#tls:
|
||||
#- secretName: whatever.A.B.C.D.nip.io
|
||||
# hosts:
|
||||
# - whatever.A.B.C.D.nip.io
|
||||
rules:
|
||||
- host: whatever.A.B.C.D.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: whatever
|
||||
port:
|
||||
number: 1234
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
#tls:
|
||||
#- secretName: whatever.A.B.C.D.nip.io
|
||||
# hosts:
|
||||
# - whatever.A.B.C.D.nip.io
|
||||
rules:
|
||||
- host: whatever.A.B.C.D.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: whatever
|
||||
servicePort: 1234
|
||||
@@ -1 +0,0 @@
|
||||
ingress-v1beta1.yaml
|
||||
17
k8s/ingress.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
#tls:
|
||||
#- secretName: whatever.A.B.C.D.nip.io
|
||||
# hosts:
|
||||
# - whatever.A.B.C.D.nip.io
|
||||
rules:
|
||||
- host: whatever.A.B.C.D.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: whatever
|
||||
servicePort: 1234
|
||||
@@ -1,50 +1,49 @@
|
||||
# This is a local copy of:
|
||||
# https://github.com/rancher/local-path-provisioner/blob/master/deploy/local-path-storage.yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: local-path-storage
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: local-path-provisioner-service-account
|
||||
namespace: local-path-storage
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: local-path-provisioner-role
|
||||
namespace: local-path-storage
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "nodes", "persistentvolumeclaims", "configmaps" ]
|
||||
verbs: [ "get", "list", "watch" ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "endpoints", "persistentvolumes", "pods" ]
|
||||
verbs: [ "*" ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "events" ]
|
||||
verbs: [ "create", "patch" ]
|
||||
- apiGroups: [ "storage.k8s.io" ]
|
||||
resources: [ "storageclasses" ]
|
||||
verbs: [ "get", "list", "watch" ]
|
||||
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes", "persistentvolumeclaims"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints", "persistentvolumes", "pods"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["create", "patch"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["storageclasses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: local-path-provisioner-bind
|
||||
namespace: local-path-storage
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: local-path-provisioner-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: local-path-provisioner-service-account
|
||||
namespace: local-path-storage
|
||||
|
||||
- kind: ServiceAccount
|
||||
name: local-path-provisioner-service-account
|
||||
namespace: local-path-storage
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@@ -63,28 +62,27 @@ spec:
|
||||
spec:
|
||||
serviceAccountName: local-path-provisioner-service-account
|
||||
containers:
|
||||
- name: local-path-provisioner
|
||||
image: rancher/local-path-provisioner:v0.0.19
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- local-path-provisioner
|
||||
- --debug
|
||||
- start
|
||||
- --config
|
||||
- /etc/config/config.json
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /etc/config/
|
||||
env:
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: local-path-provisioner
|
||||
image: rancher/local-path-provisioner:v0.0.8
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- local-path-provisioner
|
||||
- --debug
|
||||
- start
|
||||
- --config
|
||||
- /etc/config/config.json
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /etc/config/
|
||||
env:
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
volumes:
|
||||
- name: config-volume
|
||||
configMap:
|
||||
name: local-path-config
|
||||
|
||||
---
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
@@ -93,7 +91,6 @@ metadata:
|
||||
provisioner: rancher.io/local-path
|
||||
volumeBindingMode: WaitForFirstConsumer
|
||||
reclaimPolicy: Delete
|
||||
|
||||
---
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
@@ -102,59 +99,12 @@ metadata:
|
||||
namespace: local-path-storage
|
||||
data:
|
||||
config.json: |-
|
||||
{
|
||||
"nodePathMap":[
|
||||
{
|
||||
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
|
||||
"paths":["/opt/local-path-provisioner"]
|
||||
}
|
||||
]
|
||||
}
|
||||
setup: |-
|
||||
#!/bin/sh
|
||||
while getopts "m:s:p:" opt
|
||||
do
|
||||
case $opt in
|
||||
p)
|
||||
absolutePath=$OPTARG
|
||||
;;
|
||||
s)
|
||||
sizeInBytes=$OPTARG
|
||||
;;
|
||||
m)
|
||||
volMode=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -m 0777 -p ${absolutePath}
|
||||
teardown: |-
|
||||
#!/bin/sh
|
||||
while getopts "m:s:p:" opt
|
||||
do
|
||||
case $opt in
|
||||
p)
|
||||
absolutePath=$OPTARG
|
||||
;;
|
||||
s)
|
||||
sizeInBytes=$OPTARG
|
||||
;;
|
||||
m)
|
||||
volMode=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
rm -rf ${absolutePath}
|
||||
helperPod.yaml: |-
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: helper-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: helper-pod
|
||||
image: busybox
|
||||
|
||||
|
||||
{
|
||||
"nodePathMap":[
|
||||
{
|
||||
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
|
||||
"paths":["/opt/local-path-provisioner"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,61 +1,32 @@
|
||||
# This file is https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
|
||||
# But with the following arguments added to metrics-server:
|
||||
# args:
|
||||
# - --kubelet-insecure-tls
|
||||
# - --metric-resolution=5s
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: system:aggregated-metrics-reader
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
rbac.authorization.k8s.io/aggregate-to-view: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
||||
rules:
|
||||
- apiGroups: ["metrics.k8s.io"]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: metrics-server:system:auth-delegator
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:auth-delegator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-view: "true"
|
||||
name: system:aggregated-metrics-reader
|
||||
rules:
|
||||
- apiGroups:
|
||||
- metrics.k8s.io
|
||||
resources:
|
||||
- pods
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: system:metrics-server
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- nodes
|
||||
- nodes/stats
|
||||
- namespaces
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: metrics-server-auth-reader
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
@@ -67,127 +38,101 @@ subjects:
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: apiregistration.k8s.io/v1beta1
|
||||
kind: APIService
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: metrics-server:system:auth-delegator
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:auth-delegator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: system:metrics-server
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:metrics-server
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
name: v1beta1.metrics.k8s.io
|
||||
spec:
|
||||
service:
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
group: metrics.k8s.io
|
||||
version: v1beta1
|
||||
insecureSkipTLSVerify: true
|
||||
groupPriorityMinimum: 100
|
||||
versionPriority: 100
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
spec:
|
||||
ports:
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
selector:
|
||||
k8s-app: metrics-server
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: metrics-server
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
name: metrics-server
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --cert-dir=/tmp
|
||||
- --secure-port=4443
|
||||
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
|
||||
- --kubelet-use-node-status-port
|
||||
- --kubelet-insecure-tls
|
||||
- --metric-resolution=5s
|
||||
image: k8s.gcr.io/metrics-server/metrics-server:v0.4.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /livez
|
||||
port: https
|
||||
scheme: HTTPS
|
||||
periodSeconds: 10
|
||||
name: metrics-server
|
||||
ports:
|
||||
- containerPort: 4443
|
||||
name: https
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: https
|
||||
scheme: HTTPS
|
||||
periodSeconds: 10
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
volumeMounts:
|
||||
- mountPath: /tmp
|
||||
name: tmp-dir
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
priorityClassName: system-cluster-critical
|
||||
serviceAccountName: metrics-server
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: tmp-dir
|
||||
# mount in tmp so we can safely use from-scratch images and/or read-only containers
|
||||
- name: tmp-dir
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: metrics-server
|
||||
image: k8s.gcr.io/metrics-server-amd64:v0.3.3
|
||||
imagePullPolicy: Always
|
||||
volumeMounts:
|
||||
- name: tmp-dir
|
||||
mountPath: /tmp
|
||||
args:
|
||||
- --kubelet-preferred-address-types=InternalIP
|
||||
- --kubelet-insecure-tls
|
||||
- --metric-resolution=5s
|
||||
|
||||
---
|
||||
apiVersion: apiregistration.k8s.io/v1
|
||||
kind: APIService
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: metrics-server
|
||||
name: v1beta1.metrics.k8s.io
|
||||
kubernetes.io/name: "Metrics-server"
|
||||
spec:
|
||||
group: metrics.k8s.io
|
||||
groupPriorityMinimum: 100
|
||||
insecureSkipTLSVerify: true
|
||||
service:
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
version: v1beta1
|
||||
versionPriority: 100
|
||||
selector:
|
||||
k8s-app: metrics-server
|
||||
ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
targetPort: 443
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: system:metrics-server
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- nodes
|
||||
- nodes/stats
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: system:metrics-server
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:metrics-server
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: metrics-server
|
||||
namespace: kube-system
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -49,8 +49,24 @@ spec:
|
||||
- --kubernetes
|
||||
- --logLevel=INFO
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: traefik-ingress-service
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
name: web
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
name: admin
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
rules:
|
||||
@@ -74,7 +90,7 @@ rules:
|
||||
- watch
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
roleRef:
|
||||
|
||||
@@ -55,8 +55,28 @@ spec:
|
||||
- --entrypoints.https.Address=:443
|
||||
- --entrypoints.https.http.tls.certResolver=default
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: traefik-ingress-service
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
name: web
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
name: admin
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
rules:
|
||||
@@ -89,7 +109,7 @@ rules:
|
||||
- watch
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
roleRef:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
#
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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-
|
||||
@@ -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-
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -4,11 +4,7 @@ These tools can help you to create VMs on:
|
||||
|
||||
- Azure
|
||||
- EC2
|
||||
- Hetzner
|
||||
- Linode
|
||||
- OpenStack
|
||||
- OVHcloud
|
||||
- Scaleway
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -17,8 +13,7 @@ These tools can help you to create VMs on:
|
||||
- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`)
|
||||
|
||||
Depending on the infrastructure that you want to use, you also need to install
|
||||
the CLI that is specific to that cloud. For OpenStack deployments, you will
|
||||
need Terraform.
|
||||
the Azure CLI, the AWS CLI, or terraform (for OpenStack deployment).
|
||||
|
||||
And if you want to generate printable cards:
|
||||
|
||||
@@ -95,9 +90,6 @@ You're all set!
|
||||
|
||||
## `./workshopctl` Usage
|
||||
|
||||
If you run `./workshopctl` without arguments, it will show a list of
|
||||
available commands, looking like this:
|
||||
|
||||
```
|
||||
workshopctl - the orchestration workshop swiss army knife
|
||||
Commands:
|
||||
@@ -106,7 +98,32 @@ cards Generate ready-to-print cards for a group of VMs
|
||||
deploy Install Docker on a bunch of running VMs
|
||||
disableaddrchecks Disable source/destination IP address checks
|
||||
disabledocker Stop Docker Engine and don't restart it automatically
|
||||
...
|
||||
helmprom Install Helm and Prometheus
|
||||
help Show available commands
|
||||
ids (FIXME) List the instance IDs belonging to a given tag or token
|
||||
kubebins Install Kubernetes and CNI binaries but don't start anything
|
||||
kubereset Wipe out Kubernetes configuration on all nodes
|
||||
kube Setup kubernetes clusters with kubeadm (must be run AFTER deploy)
|
||||
kubetest Check that all nodes are reporting as Ready
|
||||
listall List VMs running on all configured infrastructures
|
||||
list List available groups for a given infrastructure
|
||||
netfix Disable GRO and run a pinger job on the VMs
|
||||
opensg Open the default security group to ALL ingress traffic
|
||||
ping Ping VMs in a given tag, to check that they have network access
|
||||
pssh Run an arbitrary command on all nodes
|
||||
pull_images Pre-pull a bunch of Docker images
|
||||
quotas Check our infrastructure quotas (max instances)
|
||||
remap_nodeports Remap NodePort range to 10000-10999
|
||||
retag (FIXME) Apply a new tag to a group of VMs
|
||||
ssh Open an SSH session to the first node of a tag
|
||||
start Start a group of VMs
|
||||
stop Stop (terminate, shutdown, kill, remove, destroy...) instances
|
||||
tags List groups of VMs known locally
|
||||
test Run tests (pre-flight checks) on a group of VMs
|
||||
weavetest Check that weave seems properly setup
|
||||
webssh Install a WEB SSH server on the machines (port 1080)
|
||||
wrap Run this program in a container
|
||||
www Run a web server to access card HTML and PDF
|
||||
```
|
||||
|
||||
### Summary of What `./workshopctl` Does For You
|
||||
@@ -121,8 +138,7 @@ disabledocker Stop Docker Engine and don't restart it automatically
|
||||
|
||||
### Example Steps to Launch a group of AWS Instances for a Workshop
|
||||
|
||||
- Run `./workshopctl start --infra infra/aws-us-east-2 --settings/myworkshop.yaml --students 50` to create 50 clusters
|
||||
- The number of instances will be `students × clustersize`
|
||||
- Run `./workshopctl start --infra infra/aws-us-east-2 --settings/myworkshop.yaml --count 60` to create 60 EC2 instances
|
||||
- Your local SSH key will be synced to instances under `ubuntu` user
|
||||
- AWS instances will be created and tagged based on date, and IP's stored in `prepare-vms/tags/`
|
||||
- Run `./workshopctl deploy TAG` to run `lib/postprep.py` via parallel-ssh
|
||||
@@ -232,19 +248,12 @@ If you don't have `wkhtmltopdf` installed, you will get a warning that it is a m
|
||||
|
||||
#### List tags
|
||||
|
||||
$ ./workshopctl list infra/some-infra-file
|
||||
|
||||
$ ./workshopctl listall
|
||||
|
||||
$ ./workshopctl tags
|
||||
|
||||
$ ./workshopctl inventory infra/some-infra-file
|
||||
|
||||
$ ./workshopctl inventory
|
||||
|
||||
Note: the `tags` command will show only the VMs that you have provisioned
|
||||
and deployed on the current machine (i.e. listed in the `tags` subdirectory).
|
||||
The `inventory` command will try to list all existing VMs (including the
|
||||
ones not listed in the `tags` directory, and including VMs provisioned
|
||||
through other mechanisms). It is not supported across all platforms,
|
||||
however.
|
||||
|
||||
#### Stop and destroy VMs
|
||||
|
||||
$ ./workshopctl stop TAG
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INFRACLASS=hetzner
|
||||
if ! [ -f ~/.config/hcloud/cli.toml ]; then
|
||||
warning "~/.config/hcloud/cli.toml not found."
|
||||
warning "Make sure that the Hetzner CLI (hcloud) is installed and configured."
|
||||
warn "~/.config/hcloud/cli.toml not found."
|
||||
warn "Make sure that the Hetzner CLI (hcloud) is installed and configured."
|
||||
fi
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
INFRACLASS=scaleway
|
||||
#SCW_INSTANCE_TYPE=DEV1-L
|
||||
#SCW_ZONE=fr-par-2
|
||||
|
||||
@@ -66,7 +66,7 @@ need_infra() {
|
||||
|
||||
need_tag() {
|
||||
if [ -z "$TAG" ]; then
|
||||
die "Please specify a tag. To see available tags, run: $0 tags"
|
||||
die "Please specify a tag or token. To see available tags and tokens, run: $0 list"
|
||||
fi
|
||||
if [ ! -d "tags/$TAG" ]; then
|
||||
die "Tag $TAG not found (directory tags/$TAG does not exist)."
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export AWS_DEFAULT_OUTPUT=text
|
||||
|
||||
# Ignore SSH key validation when connecting to these remote hosts.
|
||||
# (Otherwise, deployment scripts break when a VM IP address reuse.)
|
||||
SSHOPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
||||
|
||||
HELP=""
|
||||
_cmd() {
|
||||
HELP="$(printf "%s\n%-20s %s\n" "$HELP" "$1" "$2")"
|
||||
@@ -73,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
|
||||
@@ -109,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
|
||||
@@ -126,7 +113,7 @@ _cmd_deploy() {
|
||||
# If /home/docker/.ssh/id_rsa doesn't exist, copy it from the first node
|
||||
pssh "
|
||||
sudo -u docker [ -f /home/docker/.ssh/id_rsa ] ||
|
||||
ssh $SSHOPTS \$(cat /etc/name_of_first_node) sudo -u docker tar -C /home/docker -cvf- .ssh |
|
||||
ssh -o StrictHostKeyChecking=no \$(cat /etc/name_of_first_node) sudo -u docker tar -C /home/docker -cvf- .ssh |
|
||||
sudo -u docker tar -C /home/docker -xf-"
|
||||
|
||||
# if 'docker@' doesn't appear in /home/docker/.ssh/authorized_keys, copy it there
|
||||
@@ -170,27 +157,24 @@ _cmd_kubebins() {
|
||||
TAG=$1
|
||||
need_tag
|
||||
|
||||
##VERSION##
|
||||
ETCD_VERSION=v3.4.13
|
||||
K8SBIN_VERSION=v1.19.11 # Can't go to 1.20 because it requires a serviceaccount signing key.
|
||||
CNI_VERSION=v0.8.7
|
||||
pssh --timeout 300 "
|
||||
set -e
|
||||
cd /usr/local/bin
|
||||
if ! [ -x etcd ]; then
|
||||
curl -L https://github.com/etcd-io/etcd/releases/download/$ETCD_VERSION/etcd-$ETCD_VERSION-linux-amd64.tar.gz \
|
||||
##VERSION##
|
||||
curl -L https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz \
|
||||
| sudo tar --strip-components=1 --wildcards -zx '*/etcd' '*/etcdctl'
|
||||
fi
|
||||
if ! [ -x hyperkube ]; then
|
||||
##VERSION##
|
||||
curl -L https://dl.k8s.io/$K8SBIN_VERSION/kubernetes-server-linux-amd64.tar.gz \
|
||||
curl -L https://dl.k8s.io/v1.18.10/kubernetes-server-linux-amd64.tar.gz \
|
||||
| sudo tar --strip-components=3 -zx \
|
||||
kubernetes/server/bin/kube{ctl,let,-proxy,-apiserver,-scheduler,-controller-manager}
|
||||
fi
|
||||
sudo mkdir -p /opt/cni/bin
|
||||
cd /opt/cni/bin
|
||||
if ! [ -x bridge ]; then
|
||||
curl -L https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION/cni-plugins-linux-amd64-$CNI_VERSION.tgz \
|
||||
curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz \
|
||||
| sudo tar -zx
|
||||
fi
|
||||
"
|
||||
@@ -224,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 &&
|
||||
@@ -259,7 +236,7 @@ _cmd_kube() {
|
||||
pssh --timeout 200 "
|
||||
if ! i_am_first_node && [ ! -f /etc/kubernetes/kubelet.conf ]; then
|
||||
FIRSTNODE=\$(cat /etc/name_of_first_node) &&
|
||||
TOKEN=\$(ssh $SSHOPTS \$FIRSTNODE cat /tmp/token) &&
|
||||
TOKEN=\$(ssh -o StrictHostKeyChecking=no \$FIRSTNODE cat /tmp/token) &&
|
||||
sudo kubeadm join --discovery-token-unsafe-skip-ca-verification --token \$TOKEN \$FIRSTNODE:6443
|
||||
fi"
|
||||
|
||||
@@ -330,7 +307,7 @@ EOF"
|
||||
# Install the AWS IAM authenticator
|
||||
pssh "
|
||||
if [ ! -x /usr/local/bin/aws-iam-authenticator ]; then
|
||||
##VERSION##
|
||||
##VERSION##
|
||||
sudo curl -o /usr/local/bin/aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.12.7/2019-03-27/bin/linux/amd64/aws-iam-authenticator
|
||||
sudo chmod +x /usr/local/bin/aws-iam-authenticator
|
||||
fi"
|
||||
@@ -345,17 +322,13 @@ EOF"
|
||||
echo export PATH=/home/docker/.krew/bin:\\\$PATH | sudo -u docker tee -a /home/docker/.bashrc
|
||||
fi"
|
||||
|
||||
# Install k9s
|
||||
# Install k9s and popeye
|
||||
pssh "
|
||||
if [ ! -x /usr/local/bin/k9s ]; then
|
||||
VERSION=v0.24.10 &&
|
||||
FILENAME=k9s_\${VERSION}_\$(uname -s)_\$(uname -m).tar.gz &&
|
||||
curl -sSL https://github.com/derailed/k9s/releases/download/\$VERSION/\$FILENAME |
|
||||
FILENAME=k9s_\$(uname -s)_\$(uname -m).tar.gz &&
|
||||
curl -sSL https://github.com/derailed/k9s/releases/latest/download/\$FILENAME |
|
||||
sudo tar -zxvf- -C /usr/local/bin k9s
|
||||
fi"
|
||||
|
||||
# Install popeye
|
||||
pssh "
|
||||
fi
|
||||
if [ ! -x /usr/local/bin/popeye ]; then
|
||||
FILENAME=popeye_\$(uname -s)_\$(uname -m).tar.gz &&
|
||||
curl -sSL https://github.com/derailed/popeye/releases/latest/download/\$FILENAME |
|
||||
@@ -430,12 +403,12 @@ _cmd_ips() {
|
||||
done < tags/$TAG/ips.txt
|
||||
}
|
||||
|
||||
_cmd inventory "List all VMs on a given infrastructure (or all infras if no arg given)"
|
||||
_cmd_inventory() {
|
||||
_cmd list "List all VMs on a given infrastructure (or all infras if no arg given)"
|
||||
_cmd_list() {
|
||||
case "$1" in
|
||||
"")
|
||||
for INFRA in infra/*; do
|
||||
$0 inventory $INFRA
|
||||
$0 list $INFRA
|
||||
done
|
||||
;;
|
||||
*/example.*)
|
||||
@@ -448,6 +421,21 @@ _cmd_inventory() {
|
||||
esac
|
||||
}
|
||||
|
||||
_cmd listall "List VMs running on all configured infrastructures"
|
||||
_cmd_listall() {
|
||||
for infra in infra/*; do
|
||||
case $infra in
|
||||
infra/example.*)
|
||||
;;
|
||||
*)
|
||||
info "Listing infrastructure $infra:"
|
||||
need_infra $infra
|
||||
infra_list
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
_cmd maketag "Generate a quasi-unique tag for a group of instances"
|
||||
_cmd_maketag() {
|
||||
if [ -z $USER ]; then
|
||||
@@ -585,8 +573,7 @@ _cmd_ssh() {
|
||||
need_tag
|
||||
IP=$(head -1 tags/$TAG/ips.txt)
|
||||
info "Logging into $IP"
|
||||
ssh $SSHOPTS docker@$IP
|
||||
|
||||
ssh docker@$IP
|
||||
}
|
||||
|
||||
_cmd start "Start a group of VMs"
|
||||
@@ -595,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."
|
||||
@@ -725,7 +712,7 @@ _cmd_tmux() {
|
||||
IP=$(head -1 tags/$TAG/ips.txt)
|
||||
info "Opening ssh+tmux with $IP"
|
||||
rm -f /tmp/tmux-$UID/default
|
||||
ssh $SSHOPTS -t -L /tmp/tmux-$UID/default:/tmp/tmux-1001/default docker@$IP tmux new-session -As 0
|
||||
ssh -t -L /tmp/tmux-$UID/default:/tmp/tmux-1001/default docker@$IP tmux new-session -As 0
|
||||
}
|
||||
|
||||
_cmd helmprom "Install Helm and Prometheus"
|
||||
@@ -734,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 \
|
||||
@@ -744,31 +731,6 @@ _cmd_helmprom() {
|
||||
fi"
|
||||
}
|
||||
|
||||
_cmd passwords "Set individual passwords for each cluster"
|
||||
_cmd_passwords() {
|
||||
TAG=$1
|
||||
need_tag
|
||||
PASSWORDS_FILE="tags/$TAG/passwords"
|
||||
if ! [ -f "$PASSWORDS_FILE" ]; then
|
||||
error "File $PASSWORDS_FILE not found. Please create it first."
|
||||
error "It should contain one password per line."
|
||||
error "It should have as many lines as there are clusters."
|
||||
die "Aborting."
|
||||
fi
|
||||
N_CLUSTERS=$($0 ips "$TAG" | wc -l)
|
||||
N_PASSWORDS=$(wc -l < "$PASSWORDS_FILE")
|
||||
if [ "$N_CLUSTERS" != "$N_PASSWORDS" ]; then
|
||||
die "Found $N_CLUSTERS clusters and $N_PASSWORDS passwords. Aborting."
|
||||
fi
|
||||
$0 ips "$TAG" | paste "$PASSWORDS_FILE" - | while read password nodes; do
|
||||
info "Setting password for $nodes..."
|
||||
for node in $nodes; do
|
||||
echo docker:$password | ssh $SSHOPTS ubuntu@$node sudo chpasswd
|
||||
done
|
||||
done
|
||||
info "Done."
|
||||
}
|
||||
|
||||
# Sometimes, weave fails to come up on some nodes.
|
||||
# Symptom: the pods on a node are unreachable (they don't even ping).
|
||||
# Remedy: wipe out Weave state and delete weave pod on that node.
|
||||
@@ -897,7 +859,10 @@ test_vm() {
|
||||
"ls -la /home/docker/.ssh"; do
|
||||
sep "$cmd"
|
||||
echo "$cmd" \
|
||||
| ssh -A $SSHOPTS $user@$ip sudo -u docker -i \
|
||||
| ssh -A -q \
|
||||
-o "UserKnownHostsFile /dev/null" \
|
||||
-o "StrictHostKeyChecking=no" \
|
||||
$user@$ip sudo -u docker -i \
|
||||
|| {
|
||||
status=$?
|
||||
error "$cmd exit status: $status"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
if ! command -v aws >/dev/null; then
|
||||
warning "AWS CLI (aws) not found."
|
||||
warn "AWS CLI (aws) not found."
|
||||
fi
|
||||
|
||||
infra_list() {
|
||||
@@ -217,7 +217,7 @@ aws_tag_instances() {
|
||||
|
||||
aws_get_ami() {
|
||||
##VERSION##
|
||||
find_ubuntu_ami -r $AWS_DEFAULT_REGION -a ${AWS_ARCHITECTURE-amd64} -v 18.04 -t hvm:ebs -N -q
|
||||
find_ubuntu_ami -r $AWS_DEFAULT_REGION -a amd64 -v 18.04 -t hvm:ebs -N -q
|
||||
}
|
||||
|
||||
aws_greet() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
if ! command -v hcloud >/dev/null; then
|
||||
warning "Hetzner CLI (hcloud) not found."
|
||||
warn "Hetzner CLI (hcloud) not found."
|
||||
fi
|
||||
if ! [ -f ~/.config/hcloud/cli.toml ]; then
|
||||
warning "~/.config/hcloud/cli.toml not found."
|
||||
warn "~/.config/hcloud/cli.toml not found."
|
||||
fi
|
||||
|
||||
infra_list() {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
if ! command -v linode-cli >/dev/null; then
|
||||
warning "Linode CLI (linode-cli) not found."
|
||||
fi
|
||||
if ! [ -f ~/.config/linode-cli ]; then
|
||||
warning "~/.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]"
|
||||
}
|
||||
@@ -1,28 +1,20 @@
|
||||
infra_start() {
|
||||
COUNT=$1
|
||||
COUNT=$1
|
||||
|
||||
cp terraform/*.tf tags/$TAG
|
||||
(
|
||||
cd tags/$TAG
|
||||
if ! terraform init; then
|
||||
error "'terraform init' failed."
|
||||
error "If it mentions the following error message:"
|
||||
error "openpgp: signature made by unknown entity."
|
||||
error "Then you need to upgrade Terraform to 0.11.15"
|
||||
error "to upgrade its signing keys following the"
|
||||
error "codecov breach."
|
||||
die "Aborting."
|
||||
fi
|
||||
echo prefix = \"$TAG\" >> terraform.tfvars
|
||||
echo count = \"$COUNT\" >> terraform.tfvars
|
||||
terraform apply -auto-approve
|
||||
terraform output ip_addresses > ips.txt
|
||||
)
|
||||
cp terraform/*.tf tags/$TAG
|
||||
(
|
||||
cd tags/$TAG
|
||||
terraform init
|
||||
echo prefix = \"$TAG\" >> terraform.tfvars
|
||||
echo count = \"$COUNT\" >> terraform.tfvars
|
||||
terraform apply -auto-approve
|
||||
terraform output ip_addresses > ips.txt
|
||||
)
|
||||
}
|
||||
|
||||
infra_stop() {
|
||||
(
|
||||
cd tags/$TAG
|
||||
terraform destroy -auto-approve
|
||||
)
|
||||
}
|
||||
(
|
||||
cd tags/$TAG
|
||||
terraform destroy -auto-approve
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
if ! command -v scw >/dev/null; then
|
||||
warning "Scaleway CLI (scw) not found."
|
||||
warn "Scaleway CLI (scw) not found."
|
||||
fi
|
||||
if ! [ -f ~/.config/scw/config.yaml ]; then
|
||||
warning "~/.config/scw/config.yaml not found."
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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.")
|
||||
|
||||
24
prepare-vms/settings/kube101.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# 3 nodes for k8s 101 workshops
|
||||
|
||||
# Number of VMs per cluster
|
||||
clustersize: 3
|
||||
|
||||
# The hostname of each node will be clusterprefix + a number
|
||||
clusterprefix: node
|
||||
|
||||
# Jinja2 template to use to generate ready-to-cut cards
|
||||
cards_template: cards.html
|
||||
|
||||
# Use "Letter" in the US, and "A4" everywhere else
|
||||
paper_size: Letter
|
||||
|
||||
# This can be "test" or "stable"
|
||||
engine_version: stable
|
||||
|
||||
# These correspond to the version numbers visible on their respective GitHub release pages
|
||||
compose_version: 1.24.1
|
||||
machine_version: 0.14.0
|
||||
|
||||
# Password used to connect with the "docker user"
|
||||
docker_user_password: training
|
||||
|
||||
@@ -30,7 +30,7 @@ TAG=$PREFIX-$SETTINGS
|
||||
--tag $TAG \
|
||||
--infra $INFRA \
|
||||
--settings settings/$SETTINGS.yaml \
|
||||
--students $STUDENTS
|
||||
--count $STUDENTS
|
||||
|
||||
retry 5 ./workshopctl deploy $TAG
|
||||
retry 5 ./workshopctl disabledocker $TAG
|
||||
@@ -45,7 +45,7 @@ TAG=$PREFIX-$SETTINGS
|
||||
--tag $TAG \
|
||||
--infra $INFRA \
|
||||
--settings settings/$SETTINGS.yaml \
|
||||
--students $STUDENTS
|
||||
--count $((3*$STUDENTS))
|
||||
|
||||
retry 5 ./workshopctl disableaddrchecks $TAG
|
||||
retry 5 ./workshopctl deploy $TAG
|
||||
@@ -60,7 +60,7 @@ TAG=$PREFIX-$SETTINGS
|
||||
--tag $TAG \
|
||||
--infra $INFRA \
|
||||
--settings settings/$SETTINGS.yaml \
|
||||
--students $STUDENTS
|
||||
--count $((3*$STUDENTS))
|
||||
|
||||
retry 5 ./workshopctl disableaddrchecks $TAG
|
||||
retry 5 ./workshopctl deploy $TAG
|
||||
@@ -79,9 +79,10 @@ TAG=$PREFIX-$SETTINGS
|
||||
--tag $TAG \
|
||||
--infra $INFRA \
|
||||
--settings settings/$SETTINGS.yaml \
|
||||
--students $STUDENTS
|
||||
--count $((3*$STUDENTS))
|
||||
|
||||
retry 5 ./workshopctl deploy $TAG
|
||||
retry 5 ./workshopctl kube $TAG 1.19.11
|
||||
retry 5 ./workshopctl kube $TAG 1.17.13
|
||||
retry 5 ./workshopctl webssh $TAG
|
||||
retry 5 ./workshopctl tailhist $TAG
|
||||
./workshopctl cards $TAG
|
||||
|
||||
@@ -17,7 +17,6 @@ done
|
||||
DEPENDENCIES="
|
||||
ssh
|
||||
curl
|
||||
fping
|
||||
jq
|
||||
pssh
|
||||
wkhtmltopdf
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#/ /kube-halfday.yml.html 200!
|
||||
#/ /kube-fullday.yml.html 200!
|
||||
#/ /kube-twodays.yml.html 200!
|
||||
/ /kube.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
|
||||
|
||||
1404
slides/autopilot/package-lock.json
generated
@@ -3,6 +3,6 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"express": "^4.16.2",
|
||||
"socket.io": "^2.4.0"
|
||||
"socket.io": "^2.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,64 +44,6 @@ Fri Feb 20 00:28:55 UTC 2015
|
||||
|
||||
---
|
||||
|
||||
## When `^C` doesn't work...
|
||||
|
||||
Sometimes, `^C` won't be enough.
|
||||
|
||||
Why? And how can we stop the container in that case?
|
||||
|
||||
---
|
||||
|
||||
## What happens when we hit `^C`
|
||||
|
||||
`SIGINT` gets sent to the container, which means:
|
||||
|
||||
- `SIGINT` gets sent to PID 1 (default case)
|
||||
|
||||
- `SIGINT` gets sent to *foreground processes* when running with `-ti`
|
||||
|
||||
But there is a special case for PID 1: it ignores all signals!
|
||||
|
||||
- except `SIGKILL` and `SIGSTOP`
|
||||
|
||||
- except signals handled explicitly
|
||||
|
||||
TL,DR: there are many circumstances when `^C` won't stop the container.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Why is PID 1 special?
|
||||
|
||||
- PID 1 has some extra responsibilities:
|
||||
|
||||
- it starts (directly or indirectly) every other process
|
||||
|
||||
- when a process exits, its processes are "reparented" under PID 1
|
||||
|
||||
- When PID 1 exits, everything stops:
|
||||
|
||||
- on a "regular" machine, it causes a kernel panic
|
||||
|
||||
- in a container, it kills all the processes
|
||||
|
||||
- We don't want PID 1 to stop accidentally
|
||||
|
||||
- That's why it has these extra protections
|
||||
|
||||
---
|
||||
|
||||
## How to stop these containers, then?
|
||||
|
||||
- Start another terminal and forget about them
|
||||
|
||||
(for now!)
|
||||
|
||||
- We'll shortly learn about `docker kill`
|
||||
|
||||
---
|
||||
|
||||
## Run a container in the background
|
||||
|
||||
Containers can be started in the background, with the `-d` flag (daemon mode):
|
||||
|
||||
@@ -131,7 +131,7 @@ root@fcfb62f0bfde:/# figlet hello
|
||||
|_| |_|\___|_|_|\___/
|
||||
```
|
||||
|
||||
It works! 🎉
|
||||
It works! .emoji[🎉]
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -89,44 +89,6 @@ To keep things simple for now: this is the directory where our Dockerfile is loc
|
||||
|
||||
## What happens when we build the image?
|
||||
|
||||
It depends if we're using BuildKit or not!
|
||||
|
||||
If there are lots of blue lines and the first line looks like this:
|
||||
```
|
||||
[+] Building 1.8s (4/6)
|
||||
```
|
||||
... then we're using BuildKit.
|
||||
|
||||
If the output is mostly black-and-white and the first line looks like this:
|
||||
```
|
||||
Sending build context to Docker daemon 2.048kB
|
||||
```
|
||||
... then we're using the "classic" or "old-style" builder.
|
||||
|
||||
---
|
||||
|
||||
## To BuildKit or Not To BuildKit
|
||||
|
||||
Classic builder:
|
||||
|
||||
- copies the whole "build context" to the Docker Engine
|
||||
|
||||
- linear (processes lines one after the other)
|
||||
|
||||
- requires a full Docker Engine
|
||||
|
||||
BuildKit:
|
||||
|
||||
- only transfers parts of the "build context" when needed
|
||||
|
||||
- will parallelize operations (when possible)
|
||||
|
||||
- can run in non-privileged containers (e.g. on Kubernetes)
|
||||
|
||||
---
|
||||
|
||||
## With the classic builder
|
||||
|
||||
The output of `docker build` looks like this:
|
||||
|
||||
.small[
|
||||
@@ -169,7 +131,7 @@ Sending build context to Docker daemon 2.048 kB
|
||||
|
||||
* Be careful (or patient) if that directory is big and your link is slow.
|
||||
|
||||
* You can speed up the process with a [`.dockerignore`](https://docs.docker.com/engine/reference/builder/#dockerignore-file) file
|
||||
* You can speed up the process with a [`.dockerignore`](https://docs.docker.com/engine/reference/builder/#dockerignore-file) file
|
||||
|
||||
* It tells docker to ignore specific files in the directory
|
||||
|
||||
@@ -199,64 +161,6 @@ Removing intermediate container e01b294dbffd
|
||||
|
||||
---
|
||||
|
||||
## With BuildKit
|
||||
|
||||
.small[
|
||||
```bash
|
||||
[+] Building 7.9s (7/7) FINISHED
|
||||
=> [internal] load build definition from Dockerfile 0.0s
|
||||
=> => transferring dockerfile: 98B 0.0s
|
||||
=> [internal] load .dockerignore 0.0s
|
||||
=> => transferring context: 2B 0.0s
|
||||
=> [internal] load metadata for docker.io/library/ubuntu:latest 1.2s
|
||||
=> [1/3] FROM docker.io/library/ubuntu@sha256:cf31af331f38d1d7158470e095b132acd126a7180a54f263d386 3.2s
|
||||
=> => resolve docker.io/library/ubuntu@sha256:cf31af331f38d1d7158470e095b132acd126a7180a54f263d386 0.0s
|
||||
=> => sha256:cf31af331f38d1d7158470e095b132acd126a7180a54f263d386da88eb681d93 1.20kB / 1.20kB 0.0s
|
||||
=> => sha256:1de4c5e2d8954bf5fa9855f8b4c9d3c3b97d1d380efe19f60f3e4107a66f5cae 943B / 943B 0.0s
|
||||
=> => sha256:6a98cbe39225dadebcaa04e21dbe5900ad604739b07a9fa351dd10a6ebad4c1b 3.31kB / 3.31kB 0.0s
|
||||
=> => sha256:80bc30679ac1fd798f3241208c14accd6a364cb8a6224d1127dfb1577d10554f 27.14MB / 27.14MB 2.3s
|
||||
=> => sha256:9bf18fab4cfbf479fa9f8409ad47e2702c63241304c2cdd4c33f2a1633c5f85e 850B / 850B 0.5s
|
||||
=> => sha256:5979309c983a2adeff352538937475cf961d49c34194fa2aab142effe19ed9c1 189B / 189B 0.4s
|
||||
=> => extracting sha256:80bc30679ac1fd798f3241208c14accd6a364cb8a6224d1127dfb1577d10554f 0.7s
|
||||
=> => extracting sha256:9bf18fab4cfbf479fa9f8409ad47e2702c63241304c2cdd4c33f2a1633c5f85e 0.0s
|
||||
=> => extracting sha256:5979309c983a2adeff352538937475cf961d49c34194fa2aab142effe19ed9c1 0.0s
|
||||
=> [2/3] RUN apt-get update 2.5s
|
||||
=> [3/3] RUN apt-get install figlet 0.9s
|
||||
=> exporting to image 0.1s
|
||||
=> => exporting layers 0.1s
|
||||
=> => writing image sha256:3b8aee7b444ab775975dfba691a72d8ac24af2756e0a024e056e3858d5a23f7c 0.0s
|
||||
=> => naming to docker.io/library/figlet 0.0s
|
||||
```
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Understanding BuildKit output
|
||||
|
||||
- BuildKit transfers the Dockerfile and the *build context*
|
||||
|
||||
(these are the first two `[internal]` stages)
|
||||
|
||||
- Then it executes the steps defined in the Dockerfile
|
||||
|
||||
(`[1/3]`, `[2/3]`, `[3/3]`)
|
||||
|
||||
- Finally, it exports the result of the build
|
||||
|
||||
(image definition + collection of layers)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## BuildKit plain output
|
||||
|
||||
- When running BuildKit in e.g. a CI pipeline, its output will be different
|
||||
|
||||
- We can see the same output format by using `--progress=plain`
|
||||
|
||||
---
|
||||
|
||||
## The caching system
|
||||
|
||||
If you run the same build again, it will be instantaneous. Why?
|
||||
@@ -267,10 +171,10 @@ If you run the same build again, it will be instantaneous. Why?
|
||||
|
||||
* Docker uses the exact strings defined in your Dockerfile, so:
|
||||
|
||||
* `RUN apt-get install figlet cowsay`
|
||||
* `RUN apt-get install figlet cowsay `
|
||||
<br/> is different from
|
||||
<br/> `RUN apt-get install cowsay figlet`
|
||||
|
||||
|
||||
* `RUN apt-get update` is not re-executed when the mirrors are updated
|
||||
|
||||
You can force a rebuild with `docker build --no-cache ...`.
|
||||
@@ -292,7 +196,7 @@ root@91f3c974c9a1:/# figlet hello
|
||||
```
|
||||
|
||||
|
||||
Yay! 🎉
|
||||
Yay! .emoji[🎉]
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -272,45 +272,6 @@ $ docker run -it --entrypoint bash myfiglet
|
||||
root@6027e44e2955:/#
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `CMD` and `ENTRYPOINT` recap
|
||||
|
||||
- `docker run myimage` executes `ENTRYPOINT` + `CMD`
|
||||
|
||||
- `docker run myimage args` executes `ENTRYPOINT` + `args` (overriding `CMD`)
|
||||
|
||||
- `docker run --entrypoint prog myimage` executes `prog` (overriding both)
|
||||
|
||||
.small[
|
||||
| Command | `ENTRYPOINT` | `CMD` | Result
|
||||
|---------------------------------|--------------------|---------|-------
|
||||
| `docker run figlet` | none | none | Use values from base image (`bash`)
|
||||
| `docker run figlet hola` | none | none | Error (executable `hola` not found)
|
||||
| `docker run figlet` | `figlet -f script` | none | `figlet -f script`
|
||||
| `docker run figlet hola` | `figlet -f script` | none | `figlet -f script hola`
|
||||
| `docker run figlet` | none | `figlet -f script` | `figlet -f script`
|
||||
| `docker run figlet hola` | none | `figlet -f script` | Error (executable `hola` not found)
|
||||
| `docker run figlet` | `figlet -f script` | `hello` | `figlet -f script hello`
|
||||
| `docker run figlet hola` | `figlet -f script` | `hello` | `figlet -f script hola`
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## When to use `ENTRYPOINT` vs `CMD`
|
||||
|
||||
`ENTRYPOINT` is great for "containerized binaries".
|
||||
|
||||
Example: `docker run consul --help`
|
||||
|
||||
(Pretend that the `docker run` part isn't there!)
|
||||
|
||||
`CMD` is great for images with multiple binaries.
|
||||
|
||||
Example: `docker run busybox ifconfig`
|
||||
|
||||
(It makes sense to indicate *which* program we want to run!)
|
||||
|
||||
???
|
||||
|
||||
:EN:- CMD and ENTRYPOINT
|
||||
|
||||
@@ -1,40 +1,51 @@
|
||||
# Compose for development stacks
|
||||
|
||||
Dockerfile = great to build *one* container image.
|
||||
Dockerfiles are great to build container images.
|
||||
|
||||
What if we have multiple containers?
|
||||
But what if we work with a complex stack made of multiple containers?
|
||||
|
||||
What if some of them require particular `docker run` parameters?
|
||||
Eventually, we will want to write some custom scripts and automation to build, run, and connect
|
||||
our containers together.
|
||||
|
||||
How do we connect them all together?
|
||||
There is a better way: using Docker Compose.
|
||||
|
||||
... Compose solves these use-cases (and a few more).
|
||||
In this section, you will use Compose to bootstrap a development environment.
|
||||
|
||||
---
|
||||
|
||||
## Life before Compose
|
||||
## What is Docker Compose?
|
||||
|
||||
Before we had Compose, we would typically write custom scripts to:
|
||||
Docker Compose (formerly known as `fig`) is an external tool.
|
||||
|
||||
- build container images,
|
||||
Unlike the Docker Engine, it is written in Python. It's open source as well.
|
||||
|
||||
- run containers using these images,
|
||||
The general idea of Compose is to enable a very simple, powerful onboarding workflow:
|
||||
|
||||
- connect the containers together,
|
||||
|
||||
- rebuild, restart, update these images and containers.
|
||||
|
||||
---
|
||||
|
||||
## Life with Compose
|
||||
|
||||
Compose enables a simple, powerful onboarding workflow:
|
||||
|
||||
1. Checkout our code.
|
||||
1. Checkout your code.
|
||||
|
||||
2. Run `docker-compose up`.
|
||||
|
||||
3. Our app is up and running!
|
||||
3. Your app is up and running!
|
||||
|
||||
---
|
||||
|
||||
## Compose overview
|
||||
|
||||
This is how you work with Compose:
|
||||
|
||||
* You describe a set (or stack) of containers in a YAML file called `docker-compose.yml`.
|
||||
|
||||
* You run `docker-compose up`.
|
||||
|
||||
* Compose automatically pulls images, builds containers, and starts them.
|
||||
|
||||
* Compose can set up links, volumes, and other Docker options for you.
|
||||
|
||||
* Compose can run the containers in the background, or in the foreground.
|
||||
|
||||
* When containers are running in the foreground, their aggregated output is shown.
|
||||
|
||||
Before diving in, let's see a small example of Compose in action.
|
||||
|
||||
---
|
||||
|
||||
@@ -44,61 +55,20 @@ class: pic
|
||||
|
||||
---
|
||||
|
||||
## Life after Compose
|
||||
## Checking if Compose is installed
|
||||
|
||||
(Or: when do we need something else?)
|
||||
If you are using the official training virtual machines, Compose has been
|
||||
pre-installed.
|
||||
|
||||
- Compose is *not* an orchestrator
|
||||
If you are using Docker for Mac/Windows or the Docker Toolbox, Compose comes with them.
|
||||
|
||||
- It isn't designed to need to run containers on multiple nodes
|
||||
If you are on Linux (desktop or server environment), you will need to install Compose from its [release page](https://github.com/docker/compose/releases) or with `pip install docker-compose`.
|
||||
|
||||
(it can, however, work with Docker Swarm Mode)
|
||||
You can always check that it is installed by running:
|
||||
|
||||
- Compose isn't ideal if we want to run containers on Kubernetes
|
||||
|
||||
- it uses different concepts (Compose services ≠ Kubernetes services)
|
||||
|
||||
- it needs a Docker Engine (althought containerd support might be coming)
|
||||
|
||||
---
|
||||
|
||||
## First rodeo with Compose
|
||||
|
||||
1. Write Dockerfiles
|
||||
|
||||
2. Describe our stack of containers in a YAML file called `docker-compose.yml`
|
||||
|
||||
3. `docker-compose up` (or `docker-compose up -d` to run in the background)
|
||||
|
||||
4. Compose pulls and builds the required images, and starts the containers
|
||||
|
||||
5. Compose shows the combined logs of all the containers
|
||||
|
||||
(if running in the background, use `docker-compose logs`)
|
||||
|
||||
6. Hit Ctrl-C to stop the whole stack
|
||||
|
||||
(if running in the background, use `docker-compose stop`)
|
||||
|
||||
---
|
||||
|
||||
## Iterating
|
||||
|
||||
After making changes to our source code, we can:
|
||||
|
||||
1. `docker-compose build` to rebuild container images
|
||||
|
||||
2. `docker-compose up` to restart the stack with the new images
|
||||
|
||||
We can also combine both with `docker-compose up --build`
|
||||
|
||||
Compose will be smart, and only recreate the containers that have changed.
|
||||
|
||||
When working with interpreted languages:
|
||||
|
||||
- dont' rebuild each time
|
||||
|
||||
- leverage a `volumes` section instead
|
||||
```bash
|
||||
$ docker-compose --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -107,37 +77,38 @@ When working with interpreted languages:
|
||||
First step: clone the source code for the app we will be working on.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jpetazzo/trainingwheels
|
||||
cd trainingwheels
|
||||
$ cd
|
||||
$ git clone https://github.com/jpetazzo/trainingwheels
|
||||
...
|
||||
$ cd trainingwheels
|
||||
```
|
||||
|
||||
Second step: start the app.
|
||||
|
||||
Second step: start your app.
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
Watch Compose build and run the app.
|
||||
|
||||
That Compose stack exposes a web server on port 8000; try connecting to it.
|
||||
Watch Compose build and run your app with the correct parameters,
|
||||
including linking the relevant containers together.
|
||||
|
||||
---
|
||||
|
||||
## Launching Our First Stack with Compose
|
||||
|
||||
We should see a web page like this:
|
||||
Verify that the app is running at `http://<yourHostIP>:8000`.
|
||||
|
||||

|
||||
|
||||
Each time we reload, the counter should increase.
|
||||
|
||||
---
|
||||
|
||||
## Stopping the app
|
||||
|
||||
When we hit Ctrl-C, Compose tries to gracefully terminate all of the containers.
|
||||
When you hit `^C`, Compose tries to gracefully terminate all of the containers.
|
||||
|
||||
After ten seconds (or if we press `^C` again) it will forcibly kill them.
|
||||
After ten seconds (or if you press `^C` again) it will forcibly kill
|
||||
them.
|
||||
|
||||
---
|
||||
|
||||
@@ -147,13 +118,13 @@ Here is the file used in the demo:
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
version: "3"
|
||||
version: "2"
|
||||
|
||||
services:
|
||||
www:
|
||||
build: www
|
||||
ports:
|
||||
- ${PORT-8000}:5000
|
||||
- 8000:5000
|
||||
user: nobody
|
||||
environment:
|
||||
DEBUG: 1
|
||||
@@ -172,9 +143,9 @@ services:
|
||||
|
||||
A Compose file has multiple sections:
|
||||
|
||||
* `version` is mandatory. (Typically use "3".)
|
||||
* `version` is mandatory. (We should use `"2"` or later; version 1 is deprecated.)
|
||||
|
||||
* `services` is mandatory. Each service corresponds to a container.
|
||||
* `services` is mandatory. A service is one or more replicas of the same image running as containers.
|
||||
|
||||
* `networks` is optional and indicates to which networks containers should be connected.
|
||||
<br/>(By default, containers will be connected on a private, per-compose-file network.)
|
||||
@@ -193,8 +164,6 @@ A Compose file has multiple sections:
|
||||
|
||||
* Version 3 added support for deployment options (scaling, rolling updates, etc).
|
||||
|
||||
* Typically use `version: "3"`.
|
||||
|
||||
The [Docker documentation](https://docs.docker.com/compose/compose-file/)
|
||||
has excellent information about the Compose file format if you need to know more about versions.
|
||||
|
||||
@@ -232,45 +201,34 @@ For the full list, check: https://docs.docker.com/compose/compose-file/
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
## Compose commands
|
||||
|
||||
- We can use environment variables in Compose files
|
||||
We already saw `docker-compose up`, but another one is `docker-compose build`.
|
||||
|
||||
(like `$THIS` or `${THAT}`)
|
||||
It will execute `docker build` for all containers mentioning a `build` path.
|
||||
|
||||
- We can provide default values, e.g. `${PORT-8000}`
|
||||
It can also be invoked automatically when starting the application:
|
||||
|
||||
- Compose will also automatically load the environment file `.env`
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
(it should contain `VAR=value`, one per line)
|
||||
Another common option is to start containers in the background:
|
||||
|
||||
- This is a great way to customize build and run parameters
|
||||
|
||||
(base image versions to use, build and run secrets, port numbers...)
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running multiple copies of a stack
|
||||
## Check container status
|
||||
|
||||
- Copy the stack in two different directories, e.g. `front` and `frontcopy`
|
||||
It can be tedious to check the status of your containers with `docker ps`,
|
||||
especially when running multiple apps at the same time.
|
||||
|
||||
- Compose prefixes images and containers with the directory name:
|
||||
Compose makes it easier; with `docker-compose ps` you will see only the status of the
|
||||
containers of the current stack:
|
||||
|
||||
`front_www`, `front_www_1`, `front_db_1`
|
||||
|
||||
`frontcopy_www`, `frontcopy_www_1`, `frontcopy_db_1`
|
||||
|
||||
- Alternatively, use `docker-compose -p frontcopy`
|
||||
|
||||
(to set the `--project-name` of a stack, which default to the dir name)
|
||||
|
||||
- Each copy is isolated from the others (runs on a different network)
|
||||
|
||||
---
|
||||
|
||||
## Checking stack status
|
||||
|
||||
We have `ps`, `docker ps`, and similarly, `docker-compose ps`:
|
||||
|
||||
```bash
|
||||
$ docker-compose ps
|
||||
@@ -280,10 +238,6 @@ trainingwheels_redis_1 /entrypoint.sh red Up 6379/tcp
|
||||
trainingwheels_www_1 python counter.py Up 0.0.0.0:8000->5000/tcp
|
||||
```
|
||||
|
||||
Shows the status of all the containers of our stack.
|
||||
|
||||
Doesn't show the other containers.
|
||||
|
||||
---
|
||||
|
||||
## Cleaning up (1)
|
||||
@@ -327,39 +281,47 @@ Use `docker-compose down -v` to remove everything including volumes.
|
||||
|
||||
## Special handling of volumes
|
||||
|
||||
- When an image gets updated, Compose automatically creates a new container
|
||||
Compose is smart. If your container uses volumes, when you restart your
|
||||
application, Compose will create a new container, but carefully re-use
|
||||
the volumes it was using previously.
|
||||
|
||||
- The data in the old container is lost...
|
||||
|
||||
- ... Except if the container is using a *volume*
|
||||
|
||||
- Compose will then re-attach that volume to the new container
|
||||
|
||||
(and data is then retained across database upgrades)
|
||||
|
||||
- All good database images use volumes
|
||||
|
||||
(e.g. all official images)
|
||||
This makes it easy to upgrade a stateful service, by pulling its
|
||||
new image and just restarting your stack with Compose.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
## Compose project name
|
||||
|
||||
## A bit of history and trivia
|
||||
* When you run a Compose command, Compose infers the "project name" of your app.
|
||||
|
||||
- Compose was initially named "Fig"
|
||||
* By default, the "project name" is the name of the current directory.
|
||||
|
||||
- Compose is one of the only components of Docker written in Python
|
||||
* For instance, if you are in `/home/zelda/src/ocarina`, the project name is `ocarina`.
|
||||
|
||||
(almost everything else is in Go)
|
||||
* All resources created by Compose are tagged with this project name.
|
||||
|
||||
- In 2020, Docker introduced "Compose CLI":
|
||||
* The project name also appears as a prefix of the names of the resources.
|
||||
|
||||
- `docker compose` command to deploy Compose stacks to some clouds
|
||||
E.g. in the previous example, service `www` will create a container `ocarina_www_1`.
|
||||
|
||||
- progressively getting feature parity with `docker-compose`
|
||||
* The project name can be overridden with `docker-compose -p`.
|
||||
|
||||
- also provides numerous improvements (e.g. leverages BuildKit by default)
|
||||
---
|
||||
|
||||
## Running two copies of the same app
|
||||
|
||||
If you want to run two copies of the same app simultaneously, all you have to do is to
|
||||
make sure that each copy has a different project name.
|
||||
|
||||
You can:
|
||||
|
||||
* copy your code in a directory with a different name
|
||||
|
||||
* start each copy with `docker-compose -p myprojname up`
|
||||
|
||||
Each copy will run in a different network, totally isolated from the other.
|
||||
|
||||
This is ideal to debug regressions, do side-by-side comparisons, etc.
|
||||
|
||||
???
|
||||
|
||||
@@ -367,4 +329,4 @@ class: extra-details
|
||||
: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
|
||||
@@ -27,9 +27,9 @@ We will also explain the principle of overlay networks and network plugins.
|
||||
|
||||
## The Container Network Model
|
||||
|
||||
Docker has "networks".
|
||||
The CNM was introduced in Engine 1.9.0 (November 2015).
|
||||
|
||||
We can manage them with the `docker network` commands; for instance:
|
||||
The CNM adds the notion of a *network*, and a new top-level command to manipulate and see those networks: `docker network`.
|
||||
|
||||
```bash
|
||||
$ docker network ls
|
||||
@@ -41,79 +41,59 @@ eb0eeab782f4 host host
|
||||
228a4355d548 blog-prod overlay
|
||||
```
|
||||
|
||||
New networks can be created (with `docker network create`).
|
||||
---
|
||||
|
||||
(Note: networks `none` and `host` are special; let's set them aside for now.)
|
||||
## What's in a network?
|
||||
|
||||
* Conceptually, a network is a virtual switch.
|
||||
|
||||
* It can be local (to a single Engine) or global (spanning multiple hosts).
|
||||
|
||||
* A network has an IP subnet associated to it.
|
||||
|
||||
* Docker will allocate IP addresses to the containers connected to a network.
|
||||
|
||||
* Containers can be connected to multiple networks.
|
||||
|
||||
* Containers can be given per-network names and aliases.
|
||||
|
||||
* The names and aliases can be resolved via an embedded DNS server.
|
||||
|
||||
---
|
||||
|
||||
## What's a network?
|
||||
## Network implementation details
|
||||
|
||||
- Conceptually, a Docker "network" is a virtual switch
|
||||
* A network is managed by a *driver*.
|
||||
|
||||
(we can also think about it like a VLAN, or a WiFi SSID, for instance)
|
||||
* The built-in drivers include:
|
||||
|
||||
- By default, containers are connected to a single network
|
||||
* `bridge` (default)
|
||||
|
||||
(but they can be connected to zero, or many networks, even dynamically)
|
||||
* `none`
|
||||
|
||||
- Each network has its own subnet (IP address range)
|
||||
* `host`
|
||||
|
||||
- A network can be local (to a single Docker Engine) or global (span multiple hosts)
|
||||
* `macvlan`
|
||||
|
||||
- Containers can have *network aliases* providing DNS-based service discovery
|
||||
* A multi-host driver, *overlay*, is available out of the box (for Swarm clusters).
|
||||
|
||||
(and each network has its own "domain", "zone", or "scope")
|
||||
* More drivers can be provided by plugins (OVS, VLAN...)
|
||||
|
||||
* A network can have a custom IPAM (IP allocator).
|
||||
|
||||
---
|
||||
|
||||
## Service discovery
|
||||
class: extra-details
|
||||
|
||||
- A container can be given a network alias
|
||||
## Differences with the CNI
|
||||
|
||||
(e.g. with `docker run --net some-network --net-alias db ...`)
|
||||
* CNI = Container Network Interface
|
||||
|
||||
- The containers running in the same network can resolve that network alias
|
||||
* CNI is used notably by Kubernetes
|
||||
|
||||
(i.e. if they do a DNS lookup on `db`, it will give the container's address)
|
||||
* With CNI, all the nodes and containers are on a single IP network
|
||||
|
||||
- We can have a different `db` container in each network
|
||||
|
||||
(this avoids naming conflicts between different stacks)
|
||||
|
||||
- When we name a container, it automatically adds the name as a network alias
|
||||
|
||||
(i.e. `docker run --name xyz ...` is like `docker run --net-alias xyz ...`
|
||||
|
||||
---
|
||||
|
||||
## Network isolation
|
||||
|
||||
- Networks are isolated
|
||||
|
||||
- By default, containers in network A cannot reach those in network B
|
||||
|
||||
- A container connected to both networks A and B can act as a router or proxy
|
||||
|
||||
- Published ports are always reachable through the Docker host address
|
||||
|
||||
(`docker run -P ...` makes a container port available to everyone)
|
||||
|
||||
---
|
||||
|
||||
## How to use networks
|
||||
|
||||
- We typically create one network per "stack" or app that we deploy
|
||||
|
||||
- More complex apps or stacks might require multiple networks
|
||||
|
||||
(e.g. `frontend`, `backend`, ...)
|
||||
|
||||
- Networks allow us to deploy multiple copies of the same stack
|
||||
|
||||
(e.g. `prod`, `dev`, `pr-442`, ....)
|
||||
|
||||
- If we use Docker Compose, this is managed automatically for us
|
||||
* Both CNI and CNM offer the same functionality, but with very different methods
|
||||
|
||||
---
|
||||
|
||||
@@ -141,30 +121,6 @@ class: pic
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## CNM vs CNI
|
||||
|
||||
- CNM is the model used by Docker
|
||||
|
||||
- Kubernetes uses a different model, architectured around CNI
|
||||
|
||||
(CNI is a kind of API between a container engine and *CNI plugins*)
|
||||
|
||||
- Docker model:
|
||||
|
||||
- multiple isolated networks
|
||||
- per-network service discovery
|
||||
- network interconnection requires extra steps
|
||||
|
||||
- Kubernetes model:
|
||||
|
||||
- single flat network
|
||||
- per-namespace service discovery
|
||||
- network isolation requires extra steps (Network Policies)
|
||||
|
||||
---
|
||||
|
||||
## Creating a network
|
||||
|
||||
Let's create a network called `dev`.
|
||||
@@ -234,12 +190,8 @@ class: extra-details
|
||||
|
||||
## Resolving container addresses
|
||||
|
||||
Since Docker Engine 1.10, name resolution is implemented by a dynamic resolver.
|
||||
|
||||
Archeological note: when CNM was intoduced (in Docker Engine 1.9, November 2015)
|
||||
name resolution was implemented with `/etc/hosts`, and it was updated each time
|
||||
CONTAINERs were added/removed. This could cause interesting race conditions
|
||||
since `/etc/hosts` was a bind-mount (and couldn't be updated atomically).
|
||||
In Docker Engine 1.9, name resolution is implemented with `/etc/hosts`, and
|
||||
updating it each time containers are added/removed.
|
||||
|
||||
.small[
|
||||
```bash
|
||||
@@ -256,6 +208,10 @@ ff02::2 ip6-allrouters
|
||||
```
|
||||
]
|
||||
|
||||
In Docker Engine 1.10, this has been replaced by a dynamic resolver.
|
||||
|
||||
(This avoids race conditions when updating `/etc/hosts`.)
|
||||
|
||||
---
|
||||
|
||||
# Service discovery with containers
|
||||
@@ -309,12 +265,12 @@ Note: we're not using a FQDN or an IP address here; just `redis`.
|
||||
|
||||
* That container must be on the same network as the web server.
|
||||
|
||||
* It must have the right network alias (`redis`) so the application can find it.
|
||||
* It must have the right name (`redis`) so the application can find it.
|
||||
|
||||
Start the container:
|
||||
|
||||
```bash
|
||||
$ docker run --net dev --net-alias redis -d redis
|
||||
$ docker run --net dev --name redis -d redis
|
||||
```
|
||||
|
||||
---
|
||||
@@ -331,19 +287,36 @@ $ docker run --net dev --net-alias redis -d redis
|
||||
|
||||
## A few words on *scope*
|
||||
|
||||
- Container names are unique (there can be only one `--name redis`)
|
||||
* What if we want to run multiple copies of our application?
|
||||
|
||||
- Network aliases are not unique
|
||||
* Since names are unique, there can be only one container named `redis` at a time.
|
||||
|
||||
- We can have the same network alias in different networks:
|
||||
```bash
|
||||
docker run --net dev --net-alias redis ...
|
||||
docker run --net prod --net-alias redis ...
|
||||
```
|
||||
* However, we can specify the network name of our container with `--net-alias`.
|
||||
|
||||
- We can even have multiple containers with the same alias in the same network
|
||||
* `--net-alias` is scoped per network, and independent from the container name.
|
||||
|
||||
(in that case, we get multiple DNS entries, aka "DNS round robin")
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Using a network alias instead of a name
|
||||
|
||||
Let's remove the `redis` container:
|
||||
|
||||
```bash
|
||||
$ docker rm -f redis
|
||||
```
|
||||
|
||||
* `-f`: Force the removal of a running container (uses SIGKILL)
|
||||
|
||||
And create one that doesn't block the `redis` name:
|
||||
|
||||
```bash
|
||||
$ docker run --net dev --net-alias redis -d redis
|
||||
```
|
||||
|
||||
Check that the app still works (but the counter is back to 1,
|
||||
since we wiped out the old Redis container).
|
||||
|
||||
---
|
||||
|
||||
@@ -376,9 +349,7 @@ A container can have multiple network aliases.
|
||||
|
||||
Network aliases are *local* to a given network (only exist in this network).
|
||||
|
||||
Multiple containers can have the same network alias (even on the same network).
|
||||
|
||||
Since Docker Engine 1.11, resolving a network alias yields the IP addresses of all containers holding this alias.
|
||||
Multiple containers can have the same network alias (even on the same network). In Docker Engine 1.11, resolving a network alias yields the IP addresses of all containers holding this alias.
|
||||
|
||||
---
|
||||
|
||||
@@ -531,24 +502,6 @@ b2887adeb5578a01fd9c55c435cad56bbbe802350711d2743691f95743680b09
|
||||
|
||||
---
|
||||
|
||||
## Network drivers
|
||||
|
||||
* A network is managed by a *driver*.
|
||||
|
||||
* The built-in drivers include:
|
||||
|
||||
* `bridge` (default)
|
||||
* `none`
|
||||
* `host`
|
||||
* `macvlan`
|
||||
* `overlay` (for Swarm clusters)
|
||||
|
||||
* More drivers can be provided by plugins (OVS, VLAN...)
|
||||
|
||||
* A network can have a custom IPAM (IP allocator).
|
||||
|
||||
---
|
||||
|
||||
## Overlay networks
|
||||
|
||||
* The features we've seen so far only work when all containers are on a single host.
|
||||
@@ -789,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*
|
||||
|
||||
@@ -15,84 +15,53 @@ At the end of this section, you will be able to:
|
||||
|
||||
* Run a network service in a container.
|
||||
|
||||
* Connect to that network service.
|
||||
* Manipulate container networking basics.
|
||||
|
||||
* Find a container's IP address.
|
||||
|
||||
---
|
||||
|
||||
## Running a very simple service
|
||||
|
||||
- We need something small, simple, easy to configure
|
||||
|
||||
(or, even better, that doesn't require any configuration at all)
|
||||
|
||||
- Let's use the official NGINX image (named `nginx`)
|
||||
|
||||
- It runs a static web server listening on port 80
|
||||
|
||||
- It serves a default "Welcome to nginx!" page
|
||||
We will also explain the different network models used by Docker.
|
||||
|
||||
---
|
||||
|
||||
## Runing an NGINX server
|
||||
## A simple, static web server
|
||||
|
||||
Run the Docker Hub image `nginx`, which contains a basic web server:
|
||||
|
||||
```bash
|
||||
$ docker run -d -P nginx
|
||||
66b1ce719198711292c8f34f84a7b68c3876cf9f67015e752b94e189d35a204e
|
||||
```
|
||||
|
||||
- Docker will automatically pull the `nginx` image from the Docker Hub
|
||||
* Docker will download the image from the Docker Hub.
|
||||
|
||||
- `-d` / `--detach` tells Docker to run it in the background
|
||||
* `-d` tells Docker to run the image in the background.
|
||||
|
||||
- `P` / `--publish-all` tells Docker to publish all ports
|
||||
* `-P` tells Docker to make this service reachable from other computers.
|
||||
<br/>(`-P` is the short version of `--publish-all`.)
|
||||
|
||||
(publish = make them reachable from other computers)
|
||||
|
||||
- ...OK, how do we connect to our web server now?
|
||||
But, how do we connect to our web server now?
|
||||
|
||||
---
|
||||
|
||||
## Finding our web server port
|
||||
|
||||
- First, we need to find the *port number* used by Docker
|
||||
We will use `docker ps`:
|
||||
|
||||
(the NGINX container listens on port 80, but this port will be *mapped*)
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE ... PORTS ...
|
||||
e40ffb406c9e nginx ... 0.0.0.0:32768->80/tcp ...
|
||||
```
|
||||
|
||||
- We can use `docker ps`:
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE ... PORTS ...
|
||||
e40ffb406c9e nginx ... 0.0.0.0:`12345`->80/tcp ...
|
||||
```
|
||||
|
||||
- This means:
|
||||
* The web server is running on port 80 inside the container.
|
||||
|
||||
*port 12345 on the Docker host is mapped to port 80 in the container*
|
||||
* This port is mapped to port 32768 on our Docker host.
|
||||
|
||||
- Now we need to connect to the Docker host!
|
||||
We will explain the whys and hows of this port mapping.
|
||||
|
||||
---
|
||||
But first, let's make sure that everything works properly.
|
||||
|
||||
## Finding the address of the Docker host
|
||||
|
||||
- When running Docker on your Linux workstation:
|
||||
|
||||
*use `localhost`, or any IP address of your machine*
|
||||
|
||||
- When running Docker on a remote Linux server:
|
||||
|
||||
*use any IP address of the remote machine*
|
||||
|
||||
- When running Docker Desktop on Mac or Windows:
|
||||
|
||||
*use `localhost`*
|
||||
|
||||
- In other scenarios (`docker-machine`, local VM...):
|
||||
|
||||
*use the IP address of the Docker VM*
|
||||
|
||||
---
|
||||
|
||||
## Connecting to our web server (GUI)
|
||||
@@ -112,7 +81,7 @@ Make sure to use the right port number if it is different
|
||||
from the example below:
|
||||
|
||||
```bash
|
||||
$ curl localhost:12345
|
||||
$ curl localhost:32768
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -147,41 +116,17 @@ IMAGE CREATED CREATED BY
|
||||
|
||||
---
|
||||
|
||||
## Why can't we just connect to port 80?
|
||||
## Why are we mapping ports?
|
||||
|
||||
- Our Docker host has only one port 80
|
||||
* We are out of IPv4 addresses.
|
||||
|
||||
- Therefore, we can only have one container at a time on port 80
|
||||
* Containers cannot have public IPv4 addresses.
|
||||
|
||||
- Therefore, if multiple containers want port 80, only one can get it
|
||||
* They have private addresses.
|
||||
|
||||
- By default, containers *do not* get "their" port number, but a random one
|
||||
* Services have to be exposed port by port.
|
||||
|
||||
(not "random" as "crypto random", but as "it depends on various factors")
|
||||
|
||||
- We'll see later how to force a port number (including port 80!)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Using multiple IP addresses
|
||||
|
||||
*Hey, my network-fu is strong, and I have questions...*
|
||||
|
||||
- Can I publish one container on 127.0.0.2:80, and another on 127.0.0.3:80?
|
||||
|
||||
- My machine has multiple (public) IP addresses, let's say A.A.A.A and B.B.B.B.
|
||||
<br/>
|
||||
Can I have one container on A.A.A.A:80 and another on B.B.B.B:80?
|
||||
|
||||
- I have a whole IPV4 subnet, can I allocate it to my containers?
|
||||
|
||||
- What about IPV6?
|
||||
|
||||
You can do all these things when running Docker directly on Linux.
|
||||
|
||||
(On other platforms, *generally not*, but there are some exceptions.)
|
||||
* Ports have to be mapped to avoid conflicts.
|
||||
|
||||
---
|
||||
|
||||
@@ -193,7 +138,7 @@ There is a command to help us:
|
||||
|
||||
```bash
|
||||
$ docker port <containerID> 80
|
||||
0.0.0.0:12345
|
||||
32768
|
||||
```
|
||||
|
||||
---
|
||||
@@ -227,11 +172,13 @@ There are many ways to integrate containers in your network.
|
||||
* Pick a fixed port number in advance, when you generate your configuration.
|
||||
<br/>Then start your container by setting the port numbers manually.
|
||||
|
||||
* Use an orchestrator like Kubernetes or Swarm.
|
||||
<br/>The orchestrator will provide its own networking facilities.
|
||||
* Use a network plugin, connecting your containers with e.g. VLANs, tunnels...
|
||||
|
||||
Orchestrators typically provide mechanisms to enable direct container-to-container
|
||||
communication across hosts, and publishing/load balancing for inbound traffic.
|
||||
* Enable *Swarm Mode* to deploy across a cluster.
|
||||
<br/>The container will then be reachable through any node of the cluster.
|
||||
|
||||
When using Docker through an extra management layer like Mesos or Kubernetes,
|
||||
these will usually provide their own mechanism to expose containers.
|
||||
|
||||
---
|
||||
|
||||
@@ -255,34 +202,16 @@ $ docker inspect --format '{{ .NetworkSettings.IPAddress }}' <yourContainerID>
|
||||
|
||||
## Pinging our container
|
||||
|
||||
Let's try to ping our container *from another container.*
|
||||
We can test connectivity to the container using the IP address we've
|
||||
just discovered. Let's see this now by using the `ping` tool.
|
||||
|
||||
```bash
|
||||
docker run alpine ping `<ipaddress>`
|
||||
PING 172.17.0.X (172.17.0.X): 56 data bytes
|
||||
64 bytes from 172.17.0.X: seq=0 ttl=64 time=0.106 ms
|
||||
64 bytes from 172.17.0.X: seq=1 ttl=64 time=0.250 ms
|
||||
64 bytes from 172.17.0.X: seq=2 ttl=64 time=0.188 ms
|
||||
$ ping <ipAddress>
|
||||
64 bytes from <ipAddress>: icmp_req=1 ttl=64 time=0.085 ms
|
||||
64 bytes from <ipAddress>: icmp_req=2 ttl=64 time=0.085 ms
|
||||
64 bytes from <ipAddress>: icmp_req=3 ttl=64 time=0.085 ms
|
||||
```
|
||||
|
||||
When running on Linux, we can even ping that IP address directly!
|
||||
|
||||
(And connect to a container's ports even if they aren't published.)
|
||||
|
||||
---
|
||||
|
||||
## How often do we use `-p` and `-P` ?
|
||||
|
||||
- When running a stack of containers, we will often use Compose
|
||||
|
||||
- Compose will take care of exposing containers
|
||||
|
||||
(through a `ports:` section in the `docker-compose.yml` file)
|
||||
|
||||
- It is, however, fairly common to use `docker run -P` for a quick test
|
||||
|
||||
- Or `docker run -p ...` when an image doesn't `EXPOSE` a port correctly
|
||||
|
||||
---
|
||||
|
||||
## Section summary
|
||||
@@ -291,11 +220,19 @@ We've learned how to:
|
||||
|
||||
* Expose a network port.
|
||||
|
||||
* Connect to an application running in a container.
|
||||
* Manipulate container networking basics.
|
||||
|
||||
* Find a container's IP address.
|
||||
|
||||
In the next chapter, we will see how to connect
|
||||
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
|
||||
|
||||
@@ -88,45 +88,18 @@ Success!
|
||||
|
||||
## Details
|
||||
|
||||
* We can `COPY` whole directories recursively
|
||||
* You can `COPY` whole directories recursively.
|
||||
|
||||
* It is possible to do e.g. `COPY . .`
|
||||
|
||||
(but it might require some extra precautions to avoid copying too much)
|
||||
|
||||
* In older Dockerfiles, you might see the `ADD` command; consider it deprecated
|
||||
|
||||
(it is similar to `COPY` but can automatically extract archives)
|
||||
* Older Dockerfiles also have the `ADD` instruction.
|
||||
<br/>It is similar but can automatically extract archives.
|
||||
|
||||
* If we really wanted to compile C code in a container, we would:
|
||||
|
||||
* place it in a different directory, with the `WORKDIR` instruction
|
||||
* Place it in a different directory, with the `WORKDIR` instruction.
|
||||
|
||||
* even better, use the `gcc` official image
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## `.dockerignore`
|
||||
|
||||
- We can create a file named `.dockerignore`
|
||||
|
||||
(at the top-level of the build context)
|
||||
|
||||
- It can contain file names and globs to ignore
|
||||
|
||||
- They won't be sent to the builder
|
||||
|
||||
(and won't end up in the resulting image)
|
||||
|
||||
- See the [documentation] for the little details
|
||||
|
||||
(exceptions can be made with `!`, multiple directory levels with `**`...)
|
||||
|
||||
[documentation]: https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||
* Even better, use the `gcc` official image.
|
||||
|
||||
???
|
||||
|
||||
:EN:- Leveraging the build cache for faster builds
|
||||
:EN:- The build cache
|
||||
:FR:- Tirer parti du cache afin d'optimiser la vitesse de *build*
|
||||
|
||||
@@ -66,9 +66,9 @@ Adding the dependencies as a separate step means that Docker can cache more effi
|
||||
|
||||
```bash
|
||||
FROM python
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
RUN pip install -qr /tmp/requirements.txt
|
||||
WORKDIR /src
|
||||
COPY requirements.txt .
|
||||
RUN pip install -qr requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 5000
|
||||
CMD ["python", "app.py"]
|
||||
@@ -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
|
||||
|
||||
@@ -2,99 +2,4 @@
|
||||
|
||||
Let's write Dockerfiles for an existing application!
|
||||
|
||||
1. Check out the code repository
|
||||
|
||||
2. Read all the instructions
|
||||
|
||||
3. Write Dockerfiles
|
||||
|
||||
4. Build and test them individually
|
||||
|
||||
<!--
|
||||
5. Test them together with the provided Compose file
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Code repository
|
||||
|
||||
Clone the repository available at:
|
||||
|
||||
https://github.com/jpetazzo/wordsmith
|
||||
|
||||
It should look like this:
|
||||
```
|
||||
├── LICENSE
|
||||
├── README
|
||||
├── db/
|
||||
│ └── words.sql
|
||||
├── web/
|
||||
│ ├── dispatcher.go
|
||||
│ └── static/
|
||||
└── words/
|
||||
├── pom.xml
|
||||
└── src/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
The repository contains instructions in English and French.
|
||||
<br/>
|
||||
For now, we only care about the first part (about writing Dockerfiles).
|
||||
<br/>
|
||||
Place each Dockerfile in its own directory, like this:
|
||||
```
|
||||
├── LICENSE
|
||||
├── README
|
||||
├── db/
|
||||
│ ├── `Dockerfile`
|
||||
│ └── words.sql
|
||||
├── web/
|
||||
│ ├── `Dockerfile`
|
||||
│ ├── dispatcher.go
|
||||
│ └── static/
|
||||
└── words/
|
||||
├── `Dockerfile`
|
||||
├── pom.xml
|
||||
└── src/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build and test
|
||||
|
||||
Build and run each Dockerfile individually.
|
||||
|
||||
For `db`, we should be able to see some messages confirming that the data set
|
||||
was loaded successfully (some `INSERT` lines in the container output).
|
||||
|
||||
For `web` and `words`, we should be able to see some message looking like
|
||||
"server started successfully".
|
||||
|
||||
That's all we care about for now!
|
||||
|
||||
Bonus question: make sure that each container stops correctly when hitting Ctrl-C.
|
||||
|
||||
???
|
||||
|
||||
## Test with a Compose file
|
||||
|
||||
Place the following Compose file at the root of the repository:
|
||||
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
db:
|
||||
build: db
|
||||
words:
|
||||
build: words
|
||||
web:
|
||||
build: web
|
||||
ports:
|
||||
- 8888:80
|
||||
```
|
||||
|
||||
Test the whole app by bringin up the stack and connecting to port 8888.
|
||||
The code is at: https://github.com/jpetazzo/wordsmith
|
||||
|
||||
@@ -106,7 +106,7 @@ root@04c0bb0a6c07:/# figlet hello
|
||||
|_| |_|\___|_|_|\___/
|
||||
```
|
||||
|
||||
Beautiful! 😍
|
||||
Beautiful! .emoji[😍]
|
||||
|
||||
---
|
||||
|
||||
@@ -118,7 +118,7 @@ Let's check how many packages are installed there.
|
||||
|
||||
```bash
|
||||
root@04c0bb0a6c07:/# dpkg -l | wc -l
|
||||
97
|
||||
190
|
||||
```
|
||||
|
||||
* `dpkg -l` lists the packages installed in our container
|
||||
@@ -175,7 +175,7 @@ Now try to run `figlet`. Does that work?
|
||||
|
||||
* We can run *any container* on *any host*.
|
||||
|
||||
(One exception: Windows containers can only run on Windows hosts; at least for now.)
|
||||
(One exception: Windows containers cannot run on Linux machines; at least not yet.)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -56,8 +56,6 @@ Each of the following items will correspond to one layer:
|
||||
* Our application code and assets
|
||||
* Our application configuration
|
||||
|
||||
(Note: app config is generally added by orchestration facilities.)
|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||
@@ -369,44 +367,6 @@ This is similar to what we would do with `pip install`, `npm install`, etc.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Multi-arch images
|
||||
|
||||
- An image can support multiple architectures
|
||||
|
||||
- More precisely, a specific *tag* in a given *repository* can have either:
|
||||
|
||||
- a single *manifest* referencing an image for a single architecture
|
||||
|
||||
- a *manifest list* (or *fat manifest*) referencing multiple images
|
||||
|
||||
- In a *manifest list*, each image is identified by a combination of:
|
||||
|
||||
- `os` (linux, windows)
|
||||
|
||||
- `architecture` (amd64, arm, arm64...)
|
||||
|
||||
- optional fields like `variant` (for arm and arm64), `os.version` (for windows)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Working with multi-arch images
|
||||
|
||||
- The Docker Engine will pull "native" images when available
|
||||
|
||||
(images matching its own os/architecture/variant)
|
||||
|
||||
- We can ask for a specific image platform with `--platform`
|
||||
|
||||
- The Docker Engine can run non-native images thanks to QEMU+binfmt
|
||||
|
||||
(automatically on Docker Desktop; with a bit of setup on Linux)
|
||||
|
||||
---
|
||||
|
||||
## Section summary
|
||||
|
||||
We've learned how to:
|
||||
|
||||
@@ -154,7 +154,7 @@ Option 2:
|
||||
|
||||
Option 3:
|
||||
|
||||
* Use a *bind mount* to share local files with the container
|
||||
* Use a *volume* to mount local files into the container
|
||||
* Make changes locally
|
||||
* Changes are reflected in the container
|
||||
|
||||
@@ -199,28 +199,7 @@ The flag structure is:
|
||||
|
||||
* If you don't specify `rw` or `ro`, it will be `rw` by default.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Hold your horses... and your mounts
|
||||
|
||||
- The `-v /path/on/host:/path/in/container` syntax is the "old" syntax
|
||||
|
||||
- The modern syntax looks like this:
|
||||
|
||||
`--mount type=bind,source=/path/on/host,target=/path/in/container`
|
||||
|
||||
- `--mount` is more explicit, but `-v` is quicker to type
|
||||
|
||||
- `--mount` supports all mount types; `-v` doesn't support `tmpfs` mounts
|
||||
|
||||
- `--mount` fails if the path on the host doesn't exist; `-v` creates it
|
||||
|
||||
With the new syntax, our command becomes:
|
||||
```bash
|
||||
docker run --mount=type=bind,source=$(pwd),target=/src -dP namer
|
||||
```
|
||||
There will be a full chapter about volumes!
|
||||
|
||||
---
|
||||
|
||||
@@ -274,43 +253,15 @@ color: red;
|
||||
|
||||
## Understanding volumes
|
||||
|
||||
- Volumes are *not* copying or synchronizing files between the host and the container
|
||||
* Volumes are *not* copying or synchronizing files between the host and the container.
|
||||
|
||||
- Changes made in the host are immediately visible in the container (and vice versa)
|
||||
* Volumes are *bind mounts*: a kernel mechanism associating one path with another.
|
||||
|
||||
- When running on Linux:
|
||||
* Bind mounts are *kind of* similar to symbolic links, but at a very different level.
|
||||
|
||||
- volumes and bind mounts correspond to directories on the host
|
||||
* Changes made on the host or on the container will be visible on the other side.
|
||||
|
||||
- if Docker runs in a Linux VM, these directories are in the Linux VM
|
||||
|
||||
- When running on Docker Desktop:
|
||||
|
||||
- volumes correspond to directories in a small Linux VM running Docker
|
||||
|
||||
- access to bind mounts is translated to host filesystem access
|
||||
<br/>
|
||||
(a bit like a network filesystem)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Docker Desktop caveats
|
||||
|
||||
- When running Docker natively on Linux, accessing a mount = native I/O
|
||||
|
||||
- When running Docker Desktop, accessing a bind mount = file access translation
|
||||
|
||||
- That file access translation has relatively good performance *in general*
|
||||
|
||||
(watch out, however, for that big `npm install` working on a bind mount!)
|
||||
|
||||
- There are some corner cases when watching files (with mechanisms like inotify)
|
||||
|
||||
- Features like "live reload" or programs like `entr` don't always behave properly
|
||||
|
||||
(due to e.g. file attribute caching, and other interesting details!)
|
||||
(Under the hood, it's the same file anyway.)
|
||||
|
||||
---
|
||||
|
||||
@@ -446,4 +397,4 @@ We've learned how to:
|
||||
:EN:- “Containerize” a development environment
|
||||
|
||||
:FR:Développer au jour le jour
|
||||
:FR:- « Containeriser » son environnement de développement
|
||||
:FR:- « Containeriser » son environnement de développement
|
||||
@@ -298,20 +298,21 @@ virtually "free."
|
||||
|
||||
## Build targets
|
||||
|
||||
* We can also tag an intermediary stage with the following command:
|
||||
```bash
|
||||
docker build --target STAGE --tag NAME
|
||||
```
|
||||
* We can also tag an intermediary stage with `docker build --target STAGE --tag NAME`
|
||||
|
||||
* This will create an image (named `NAME`) corresponding to stage `STAGE`
|
||||
|
||||
* This can be used to easily access an intermediary stage for inspection
|
||||
|
||||
(instead of parsing the output of `docker build` to find out the image ID)
|
||||
(Instead of parsing the output of `docker build` to find out the image ID)
|
||||
|
||||
* This can also be used to describe multiple images from a single Dockerfile
|
||||
|
||||
(instead of using multiple Dockerfiles, which could go out of sync)
|
||||
(Instead of using multiple Dockerfiles, which could go out of sync)
|
||||
|
||||
* Sometimes, we want to inspect a specific intermediary build stage.
|
||||
|
||||
* Or, we want to describe multiple images using a single Dockerfile.
|
||||
|
||||
???
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
# Container network drivers
|
||||
|
||||
The Docker Engine supports different network drivers.
|
||||
The Docker Engine supports many different network drivers.
|
||||
|
||||
The built-in drivers include:
|
||||
|
||||
* `bridge` (default)
|
||||
|
||||
* `null` (for the special network called `none`)
|
||||
* `none`
|
||||
|
||||
* `host` (for the special network called `host`)
|
||||
* `host`
|
||||
|
||||
* `container` (that one is a bit magic!)
|
||||
* `container`
|
||||
|
||||
The network is selected with `docker run --net ...`.
|
||||
|
||||
Each network is managed by a driver.
|
||||
The driver is selected with `docker run --net ...`.
|
||||
|
||||
The different drivers are explained with more details on the following slides.
|
||||
|
||||
@@ -84,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"
|
||||
|
||||
@@ -11,10 +11,10 @@ class State(object):
|
||||
self.section_title = None
|
||||
self.section_start = 0
|
||||
self.section_slides = 0
|
||||
self.parts = {}
|
||||
self.modules = {}
|
||||
self.sections = {}
|
||||
def show(self):
|
||||
if self.section_title.startswith("part-"):
|
||||
if self.section_title.startswith("module-"):
|
||||
return
|
||||
print("{0.section_title}\t{0.section_start}\t{0.section_slides}".format(self))
|
||||
self.sections[self.section_title] = self.section_slides
|
||||
@@ -38,10 +38,10 @@ for line in open(sys.argv[1]):
|
||||
if line == "--":
|
||||
state.current_slide += 1
|
||||
toc_links = re.findall("\(#toc-(.*)\)", line)
|
||||
if toc_links and state.section_title.startswith("part-"):
|
||||
if state.section_title not in state.parts:
|
||||
state.parts[state.section_title] = []
|
||||
state.parts[state.section_title].append(toc_links[0])
|
||||
if toc_links and state.section_title.startswith("module-"):
|
||||
if state.section_title not in state.modules:
|
||||
state.modules[state.section_title] = []
|
||||
state.modules[state.section_title].append(toc_links[0])
|
||||
# This is really hackish
|
||||
if line.startswith("class:"):
|
||||
for klass in EXCLUDED:
|
||||
@@ -51,7 +51,7 @@ for line in open(sys.argv[1]):
|
||||
|
||||
state.show()
|
||||
|
||||
for part in sorted(state.parts, key=lambda f: int(f.split("-")[1])):
|
||||
part_size = sum(state.sections[s] for s in state.parts[part])
|
||||
print("{}\t{}\t{}".format("total size for", part, part_size))
|
||||
for module in sorted(state.modules, key=lambda f: int(f.split("-")[1])):
|
||||
module_size = sum(state.sections[s] for s in state.modules[module])
|
||||
print("{}\t{}\t{}".format("total size for", module, module_size))
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 231 KiB |
|
Before Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 90 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 99 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 110 KiB |
@@ -112,10 +112,7 @@ TEMPLATE="""<html>
|
||||
{% for item in all_past_workshops %}
|
||||
<tr>
|
||||
<td>{{ item.title }}</td>
|
||||
<td>{% if item.slides %}<a class="slides" href="{{ item.slides }}" />
|
||||
{% else %}
|
||||
<p class="details">{{ item.status }}</p>
|
||||
{% endif %}</td>
|
||||
<td><a class="slides" href="{{ item.slides }}" /></td>
|
||||
{% if item.video %}
|
||||
<td><a class="video" href="{{ item.video }}" /></td>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,153 +1,3 @@
|
||||
- date: [2021-09-27, 2021-09-29]
|
||||
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/
|
||||
#slides: https://2021-05-enix.container.training/1.yml.html
|
||||
|
||||
- date: [2021-10-04, 2021-10-07]
|
||||
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/
|
||||
#slides: https://2021-05-enix.container.training/2.yml.html
|
||||
|
||||
- date: [2021-10-11, 2021-10-12]
|
||||
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/
|
||||
#slides: https://2021-05-enix.container.training/3.yml.html
|
||||
|
||||
- date: [2021-11-08, 2021-11-16]
|
||||
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/
|
||||
#slides: https://2021-05-enix.container.training/4.yml.html
|
||||
|
||||
- date: [2021-11-18, 2021-11-19]
|
||||
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/
|
||||
#slides: https://2021-05-enix.container.training/5.yml.html
|
||||
|
||||
- date: [2021-05-10, 2021-05-12]
|
||||
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/
|
||||
slides: https://2021-05-enix.container.training/1.yml.html
|
||||
|
||||
- date: [2021-05-17, 2021-05-20]
|
||||
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/
|
||||
slides: https://2021-05-enix.container.training/2.yml.html
|
||||
|
||||
- date: [2021-05-24, 2021-05-25]
|
||||
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/
|
||||
slides: https://2021-05-enix.container.training/3.yml.html
|
||||
|
||||
- date: [2021-05-26, 2021-05-28]
|
||||
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/
|
||||
slides: https://2021-05-enix.container.training/4.yml.html
|
||||
|
||||
- date: [2021-05-31, 2021-06-01]
|
||||
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/
|
||||
slides: https://2021-05-enix.container.training/5.yml.html
|
||||
|
||||
- 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/
|
||||
slides: https://2021-02-enix.container.training/1.yml.html
|
||||
|
||||
- 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/
|
||||
slides: https://2021-02-enix.container.training/2.yml.html
|
||||
|
||||
- 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/
|
||||
slides: https://2021-02-enix.container.training/3.yml.html
|
||||
|
||||
- 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/
|
||||
slides: https://2021-02-enix.container.training/4.yml.html
|
||||
|
||||
- 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/
|
||||
slides: https://2021-02-enix.container.training/5.yml.html
|
||||
|
||||
- date: [2020-10-05, 2020-10-06]
|
||||
country: www
|
||||
city: streaming
|
||||
@@ -156,7 +6,6 @@
|
||||
title: Docker intensif (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-10-enix.container.training/1.yml.html
|
||||
|
||||
- date: [2020-10-07, 2020-10-09]
|
||||
country: www
|
||||
@@ -166,7 +15,6 @@
|
||||
title: Fondamentaux Kubernetes (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-10-enix.container.training/2.yml.html
|
||||
|
||||
- date: 2020-10-12
|
||||
country: www
|
||||
@@ -176,7 +24,6 @@
|
||||
title: Packaging pour Kubernetes (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-10-enix.container.training/3.yml.html
|
||||
|
||||
- date: [2020-10-13, 2020-10-14]
|
||||
country: www
|
||||
@@ -186,7 +33,6 @@
|
||||
title: Kubernetes avancé (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-10-enix.container.training/4.yml.html
|
||||
|
||||
- date: [2020-10-19, 2020-10-20]
|
||||
country: www
|
||||
@@ -196,7 +42,6 @@
|
||||
title: Opérer Kubernetes (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-10-enix.container.training/5.yml.html
|
||||
|
||||
- date: [2020-09-28, 2020-10-01]
|
||||
country: www
|
||||
@@ -205,7 +50,6 @@
|
||||
speaker: jpetazzo
|
||||
title: Advanced Kubernetes Concepts
|
||||
attend: https://skillsmatter.com/courses/700-advanced-kubernetes-concepts-workshop-jerome-petazzoni
|
||||
slides: https://2020-09-skillsmatter.container.training/
|
||||
|
||||
- date: [2020-08-29, 2020-08-30]
|
||||
country: www
|
||||
@@ -241,7 +85,6 @@
|
||||
title: Docker intensif (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-06-enix.container.training/1.yml.html
|
||||
|
||||
- date: [2020-06-17, 2020-06-19]
|
||||
country: www
|
||||
@@ -251,7 +94,6 @@
|
||||
title: Fondamentaux Kubernetes (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-06-enix.container.training/2.yml.html
|
||||
|
||||
- date: 2020-06-22
|
||||
country: www
|
||||
@@ -261,7 +103,6 @@
|
||||
title: Packaging pour Kubernetes (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-06-enix.container.training/3.yml.html
|
||||
|
||||
- date: [2020-06-23, 2020-06-24]
|
||||
country: www
|
||||
@@ -271,7 +112,6 @@
|
||||
title: Kubernetes avancé (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-06-enix.container.training/4.yml.html
|
||||
|
||||
- date: [2020-06-25, 2020-06-26]
|
||||
country: www
|
||||
@@ -281,8 +121,6 @@
|
||||
title: Opérer Kubernetes (en français)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/online/
|
||||
slides: https://2020-06-enix.container.training/5.yml.html
|
||||
|
||||
|
||||
- date: [2020-06-09, 2020-06-11]
|
||||
country: www
|
||||
|
||||
@@ -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
|
||||
@@ -134,17 +134,3 @@ installed and set up `kubectl` to communicate with your cluster.
|
||||
|
||||
:EN:- Securely accessing internal services
|
||||
:FR:- Accès sécurisé aux services internes
|
||||
|
||||
:T: Accessing internal services from our local machine
|
||||
|
||||
:Q: What's the advantage of "kubectl port-forward" compared to a NodePort?
|
||||
:A: It can forward arbitrary protocols
|
||||
:A: It doesn't require Kubernetes API credentials
|
||||
:A: It offers deterministic load balancing (instead of random)
|
||||
:A: ✔️It doesn't expose the service to the public
|
||||
|
||||
:Q: What's the security concept behind "kubectl port-forward"?
|
||||
:A: ✔️We authenticate with the Kubernetes API, and it forwards connections on our behalf
|
||||
:A: It detects our source IP address, and only allows connections coming from it
|
||||
:A: It uses end-to-end mTLS (mutual TLS) to authenticate our connections
|
||||
:A: There is no security (as long as it's running, anyone can connect from anywhere)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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`)
|
||||
|
||||
???
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -218,24 +223,6 @@ spec:
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Automatic TLS Ingress with annotations
|
||||
|
||||
- It is also possible to annotate Ingress resources for cert-manager
|
||||
|
||||
- If we annotate an Ingress resource with `cert-manager.io/cluster-issuer=xxx`:
|
||||
|
||||
- cert-manager will detect that annotation
|
||||
|
||||
- it will obtain a certificate using the specified ClusterIssuer (`xxx`)
|
||||
|
||||
- it will store the key and certificate in the specified Secret
|
||||
|
||||
- Note: the Ingress still needs the `tls` section with `secretName` and `hosts`
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Let's Encrypt and nip.io
|
||||
|
||||
- Let's Encrypt has [rate limits](https://letsencrypt.org/docs/rate-limits/) per domain
|
||||
@@ -255,5 +242,3 @@ class: extra-details
|
||||
|
||||
:EN:- Obtaining certificates with cert-manager
|
||||
:FR:- Obtenir des certificats avec cert-manager
|
||||
|
||||
:T: Obtaining TLS certificates with cert-manager
|
||||
|
||||
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -220,41 +220,6 @@ class: extra-details
|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||

|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## How many nodes should a cluster have?
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -187,7 +187,7 @@ Note: we can update a CRD without having to re-create the corresponding resource
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI v3 schema example
|
||||
## OpenAPI v3 scheme exapmle
|
||||
|
||||
This is what we have in @@LINK[k8s/coffee-3.yaml]:
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
# Exercise — sealed secrets
|
||||
|
||||
This is a "combo exercise" to practice the following concepts:
|
||||
|
||||
- Secrets (mounting them in containers)
|
||||
|
||||
- RBAC (granting specific permissions to specific users)
|
||||
|
||||
- Operators (specifically, sealed secrets)
|
||||
|
||||
- Migrations (copying/transferring resources from a cluster to another)
|
||||
|
||||
For this exercise, you will need two clusters.
|
||||
|
||||
(It can be two local clusters.)
|
||||
|
||||
We will call them "source cluster" and "target cluster".
|
||||
|
||||
---
|
||||
|
||||
## Step 1 (easy)
|
||||
|
||||
- Install the sealed secrets operator on both clusters
|
||||
|
||||
- On source cluster, create a Namespace called `dev`
|
||||
|
||||
- Create two sealed secrets, `verysecure` and `veryverysecure`
|
||||
|
||||
(the content doesn't matter; put a random string of your choice)
|
||||
|
||||
- Create a Deployment called `app` using both secrets
|
||||
|
||||
(use a mount or environment variables; whatever you prefer!)
|
||||
|
||||
- Verify that the secrets are available to the Deployment
|
||||
|
||||
---
|
||||
|
||||
## Step 2 (medium)
|
||||
|
||||
- Create another Namespace called `prod`
|
||||
|
||||
(on the source cluster)
|
||||
|
||||
- Create the same Deployment `app` using both secrets
|
||||
|
||||
- Verify that the secrets are available to the Deployment
|
||||
|
||||
---
|
||||
|
||||
## Step 3 (hard)
|
||||
|
||||
- On the target cluster, create a Namespace called `prod`
|
||||
|
||||
- Create the `app` Deployment and both sealed secrets
|
||||
|
||||
(do not copy the Secrets; only the sealed secrets)
|
||||
|
||||
- Check the next slide if you need a hint!
|
||||
|
||||
--
|
||||
|
||||
- You will have to copy the Sealed Secret private key
|
||||
|
||||
---
|
||||
|
||||
## Step 4 (medium)
|
||||
|
||||
On the target cluster, create the Namespace `dev`.
|
||||
|
||||
Let's say that user `alice` has access to the target cluster.
|
||||
|
||||
(You can use `kubectl --as=alice` to impersonate her.)
|
||||
|
||||
We want Alice to be able to:
|
||||
|
||||
- deploy the whole application
|
||||
|
||||
- access the `verysecure` secret
|
||||
|
||||
- but *not* the `veryverysecure` secret
|
||||
|
||||
---
|
||||
|
||||
## Step 5 (hard)
|
||||
|
||||
- Make sure that Alice can view the logs of the Deployment
|
||||
|
||||
- Can you think of a way for Alice to access the `veryverysecure` Secret?
|
||||
|
||||
(check next slide for a hint)
|
||||
|
||||
--
|
||||
|
||||
- `kubectl exec`, maybe?
|
||||
|
||||
--
|
||||
|
||||
- Can you think of a way to prevent that?
|
||||
@@ -1,447 +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!
|
||||
|
||||
---
|
||||
|
||||
## Ingress
|
||||
|
||||
- We will assume that we have a domain name pointing to our cluster
|
||||
|
||||
(i.e. with a wildcard record pointing to at least one node of the cluster)
|
||||
|
||||
- We will get traffic in the cluster by leveraging `ExternalIPs` services
|
||||
|
||||
(but it would be easy to use `LoadBalancer` services instead)
|
||||
|
||||
- We will use Traefik as the ingress controller
|
||||
|
||||
(but any other one should work too)
|
||||
|
||||
- We will use cert-manager to obtain certificates with Let's Encrypt
|
||||
|
||||
---
|
||||
|
||||
## Other details
|
||||
|
||||
- We will deploy GitLab with its official Helm chart
|
||||
|
||||
- It will still require a bunch of parameters and customization
|
||||
|
||||
- We also need a Storage Class
|
||||
|
||||
(unless our cluster already has one, of course)
|
||||
|
||||
- We suggest the [Rancher local path provisioner](https://github.com/rancher/local-path-provisioner)
|
||||
|
||||
---
|
||||
|
||||
## Setting everything up
|
||||
|
||||
1. `git clone https://github.com/jpetazzo/kubecoin`
|
||||
|
||||
2. `export EMAIL=xxx@example.com DOMAIN=awesome-kube-ci.io`
|
||||
|
||||
(we need a real email address and a domain pointing to the cluster!)
|
||||
|
||||
3. `. setup-gitlab-on-k8s.rc`
|
||||
|
||||
(this doesn't do anything, but defines a number of helper functions)
|
||||
|
||||
4. Execute each helper function, one after another
|
||||
|
||||
(try `do_[TAB]` to see these functions)
|
||||
|
||||
---
|
||||
|
||||
## Local Storage
|
||||
|
||||
`do_1_localstorage`
|
||||
|
||||
Applies the YAML directly from Rancher's repository.
|
||||
|
||||
Annotate the Storage Class so that it becomes the default one.
|
||||
|
||||
---
|
||||
|
||||
## Traefik
|
||||
|
||||
`do_2_traefik_with_externalips`
|
||||
|
||||
Install the official Traefik Helm chart.
|
||||
|
||||
Instead of a `LoadBalancer` service, use a `ClusterIP` with `ExternalIPs`.
|
||||
|
||||
Automatically infer the `ExternalIPs` from `kubectl get nodes`.
|
||||
|
||||
Enable TLS.
|
||||
|
||||
---
|
||||
|
||||
## cert-manager
|
||||
|
||||
`do_3_certmanager`
|
||||
|
||||
Install cert-manager using their official YAML.
|
||||
|
||||
Easy-peasy.
|
||||
|
||||
---
|
||||
|
||||
## Certificate issuers
|
||||
|
||||
`do_4_issuers`
|
||||
|
||||
Create a couple of `ClusterIssuer` resources for cert-manager.
|
||||
|
||||
(One for the staging Let's Encrypt environment, one for production.)
|
||||
|
||||
Note: this requires to specify a valid `$EMAIL` address!
|
||||
|
||||
Note: if this fails, wait a bit and try again (cert-manager needs to be up).
|
||||
|
||||
---
|
||||
|
||||
## GitLab
|
||||
|
||||
`do_5_gitlab`
|
||||
|
||||
Deploy GitLab using their official Helm chart.
|
||||
|
||||
We pass a lot of parameters to this chart:
|
||||
- the domain name to use
|
||||
- disable GitLab's own ingress and cert-manager
|
||||
- annotate the ingress resources so that cert-manager kicks in
|
||||
- bind the shell service (git over SSH) to port 222 to avoid conflict
|
||||
- use ExternalIPs for that shell service
|
||||
|
||||
Note: on modest cloud instances, it can take 10 minutes for GitLab to come up.
|
||||
|
||||
We can check the status with `kubectl get pods --namespace=gitlab`
|
||||
|
||||
---
|
||||
|
||||
## Log into GitLab and configure it
|
||||
|
||||
`do_6_showlogin`
|
||||
|
||||
This will get the GitLab root password (stored in a Secret).
|
||||
|
||||
Then we need to:
|
||||
- log into GitLab
|
||||
- add our SSH key (top-right user menu → settings, then SSH keys on the left)
|
||||
- create a project (using the + menu next to the search bar on top)
|
||||
- go to project configuration (on the left, settings → CI/CD)
|
||||
- add a `KUBECONFIG` file variable with the content of our `.kube/config` file
|
||||
- go to settings → access tokens to create a read-only registry token
|
||||
- add variables `REGISTRY_USER` and `REGISTRY_PASSWORD` with that token
|
||||
- push our repo (`git remote add gitlab ...` then `git push gitlab ...`)
|
||||
|
||||
---
|
||||
|
||||
## Monitoring progress and troubleshooting
|
||||
|
||||
- Click on "CI/CD" in the left bar to view pipelines
|
||||
|
||||
- If you see a permission issue mentioning `system:serviceaccount:gitlab:...`:
|
||||
|
||||
*make sure you did set `KUBECONFIG` correctly!*
|
||||
|
||||
- GitLab will create namespaces named `gl-<user>-<project>`
|
||||
|
||||
- At the end of the deployment, the web UI will be available on some unique URL
|
||||
|
||||
(`http://<user>-<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
|
||||
|
||||
(look in the pipeline configuration file to see how it's done!)
|
||||
|
||||
---
|
||||
|
||||
## Let's talk about build
|
||||
|
||||
- There are many ways to build container images on Kubernetes
|
||||
|
||||
- ~~And they all suck~~ Many of them have inconveniencing issues
|
||||
|
||||
- Let's do a quick review!
|
||||
|
||||
---
|
||||
|
||||
## Docker-based approaches
|
||||
|
||||
- Bind-mount the Docker socket
|
||||
|
||||
- very easy, but requires Docker Engine
|
||||
- build resource usage "evades" Kubernetes scheduler
|
||||
- insecure
|
||||
|
||||
- Docker-in-Docker in a pod
|
||||
|
||||
- requires privileged pod
|
||||
- insecure
|
||||
- approaches like rootless or sysbox might help in the future
|
||||
|
||||
- External build host
|
||||
|
||||
- more secure
|
||||
- requires resources outside of the Kubernetes cluster
|
||||
|
||||
---
|
||||
|
||||
## Non-privileged builders
|
||||
|
||||
- Kaniko
|
||||
|
||||
- each build runs in its own containers or pod
|
||||
- no caching by default
|
||||
- registry-based caching is possible
|
||||
|
||||
- BuildKit / `docker buildx`
|
||||
|
||||
- can leverage Docker Engine or long-running Kubernetes worker pod
|
||||
- supports distributed, multi-arch build farms
|
||||
- basic caching out of the box
|
||||
- can also leverage registry-based caching
|
||||
|
||||
---
|
||||
|
||||
## Other approaches
|
||||
|
||||
- Ditch the Dockerfile!
|
||||
|
||||
- bazel
|
||||
|
||||
- jib
|
||||
|
||||
- ko
|
||||
|
||||
- etc.
|
||||
|
||||
---
|
||||
|
||||
## Discussion
|
||||
|
||||
- Our CI/CD workflow is just *one* of the many possibilities
|
||||
|
||||
- It would be nice to add some actual unit or e2e tests
|
||||
|
||||
- Map the production namespace to a "real" domain name
|
||||
|
||||
- Automatically remove older staging environments
|
||||
|
||||
(see e.g. [kube-janitor](https://codeberg.org/hjacobs/kube-janitor))
|
||||
|
||||
- Deploy production to a separate cluster
|
||||
|
||||
- Better segregate permissions
|
||||
|
||||
(don't give `cluster-admin` to the GitLab pipeline)
|
||||
|
||||
---
|
||||
|
||||
## Pros
|
||||
|
||||
- GitLab is an amazing, open source, all-in-one platform
|
||||
|
||||
- Available as hosted, community, or enterprise editions
|
||||
|
||||
- Rich ecosystem, very customizable
|
||||
|
||||
- Can run on Kubernetes, or somewhere else
|
||||
|
||||
---
|
||||
|
||||
## Cons
|
||||
|
||||
- It can be difficult to use components separately
|
||||
|
||||
(e.g. use a different registry, or a different job runner)
|
||||
|
||||
- More than one way to configure it
|
||||
|
||||
(it's not an opinionated platform)
|
||||
|
||||
- Not "Kubernetes-native"
|
||||
|
||||
(for instance, jobs are not Kubernetes jobs)
|
||||
|
||||
- Job latency could be improved
|
||||
|
||||
*Note: most of these drawbacks are the flip side of the "pros" on the previous slide!*
|
||||
|
||||
???
|
||||
|
||||
:EN:- CI/CD with GitLab
|
||||
:FR:- CI/CD avec GitLab
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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!
|
||||
|
||||
---
|
||||
|
||||
@@ -86,7 +22,7 @@
|
||||
|
||||
- On April 30th 2020, Helm was the 10th project to *graduate* within the CNCF
|
||||
|
||||
🎉
|
||||
.emoji[🎉]
|
||||
|
||||
(alongside Containerd, Prometheus, and Kubernetes itself)
|
||||
|
||||
@@ -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,19 +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/my-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.
|
||||
@@ -519,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/
|
||||
```
|
||||
|
||||
]
|
||||
@@ -547,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||