mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-03-01 08:50:20 +00:00
Compare commits
89 Commits
kube-2019-
...
2020-01-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b1b942b21 | ||
|
|
0f046ed78c | ||
|
|
e2f3034a96 | ||
|
|
c5ed86c92b | ||
|
|
8933e6aa1b | ||
|
|
784b2a3e4e | ||
|
|
f3bbd6377b | ||
|
|
04d3a7b360 | ||
|
|
a32df01165 | ||
|
|
ba323cb4e6 | ||
|
|
745a435a1a | ||
|
|
db276af182 | ||
|
|
87462939d9 | ||
|
|
7d6ab6974d | ||
|
|
ae606b9c40 | ||
|
|
da9921d68a | ||
|
|
3e9a939578 | ||
|
|
328a2edaaf | ||
|
|
1f826d7993 | ||
|
|
cff9cbdfbb | ||
|
|
3ea6b730c8 | ||
|
|
1c6c76162f | ||
|
|
52bafdb57e | ||
|
|
c4d9e6b3e1 | ||
|
|
5160dd39a0 | ||
|
|
3c1220be83 | ||
|
|
6a814cf039 | ||
|
|
1385a1bae2 | ||
|
|
68a6546276 | ||
|
|
8a2ca450ee | ||
|
|
6e8ac173e0 | ||
|
|
97e68ae185 | ||
|
|
148ddd5174 | ||
|
|
e8eb11e257 | ||
|
|
fe9b56572e | ||
|
|
7281ca3ca0 | ||
|
|
34a17aa097 | ||
|
|
b37dd85eff | ||
|
|
4811420d55 | ||
|
|
a824afec85 | ||
|
|
89d9fcb1c4 | ||
|
|
5b488fbe62 | ||
|
|
6d01a9d813 | ||
|
|
cb81469170 | ||
|
|
c595a337e4 | ||
|
|
03d2d0bc5d | ||
|
|
2c46106792 | ||
|
|
291d2a6c92 | ||
|
|
f73fb92832 | ||
|
|
e9e2fa0e50 | ||
|
|
a0162d37f1 | ||
|
|
a61b69ad9a | ||
|
|
3388db4272 | ||
|
|
d2d901302f | ||
|
|
1fae4253bc | ||
|
|
f7f5ab1304 | ||
|
|
7addacef22 | ||
|
|
0136391ab5 | ||
|
|
ed27ad1d1e | ||
|
|
c15aa708df | ||
|
|
5749348883 | ||
|
|
bc885f3dca | ||
|
|
bbe35a3901 | ||
|
|
eb17b4c628 | ||
|
|
a4d50a5439 | ||
|
|
98d2b79c97 | ||
|
|
8320534a5c | ||
|
|
74ece65947 | ||
|
|
7444f8d71e | ||
|
|
c9bc417a32 | ||
|
|
7d4331477a | ||
|
|
ff132fd728 | ||
|
|
4ec7b1d7f4 | ||
|
|
e08e7848ed | ||
|
|
be6afa3e5e | ||
|
|
c340d909de | ||
|
|
b667cf7cfc | ||
|
|
e04998e9cd | ||
|
|
84198b3fdc | ||
|
|
5c161d2090 | ||
|
|
0fc7c2316c | ||
|
|
fb64c0d68f | ||
|
|
23aaf7f58c | ||
|
|
6cbcc4ae69 | ||
|
|
0b80238736 | ||
|
|
4c285b5318 | ||
|
|
2095a15728 | ||
|
|
13ba8cef9d | ||
|
|
be2374c672 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ slides/*.yml.html
|
||||
slides/autopilot/state.yaml
|
||||
slides/index.html
|
||||
slides/past.html
|
||||
slides/slides.zip
|
||||
node_modules
|
||||
|
||||
### macOS ###
|
||||
|
||||
21
k8s/canary.yaml
Normal file
21
k8s/canary.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whatever
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/service-weights: |
|
||||
whatever: 90%
|
||||
whatever-new: 10%
|
||||
spec:
|
||||
rules:
|
||||
- host: whatever.A.B.C.D.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: whatever
|
||||
servicePort: 80
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: whatever-new
|
||||
servicePort: 80
|
||||
@@ -2,8 +2,6 @@ apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: consul
|
||||
labels:
|
||||
app: consul
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
@@ -29,8 +27,6 @@ apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: consul
|
||||
labels:
|
||||
app: consul
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -72,7 +68,7 @@ spec:
|
||||
terminationGracePeriodSeconds: 10
|
||||
containers:
|
||||
- name: consul
|
||||
image: "consul:1.5"
|
||||
image: "consul:1.6"
|
||||
args:
|
||||
- "agent"
|
||||
- "-bootstrap-expect=3"
|
||||
|
||||
160
k8s/dockercoins.yaml
Normal file
160
k8s/dockercoins.yaml
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: hasher
|
||||
name: hasher
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hasher
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hasher
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/hasher:v0.1
|
||||
name: hasher
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: hasher
|
||||
name: hasher
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: hasher
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
name: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- image: redis
|
||||
name: redis
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
name: redis
|
||||
spec:
|
||||
ports:
|
||||
- port: 6379
|
||||
protocol: TCP
|
||||
targetPort: 6379
|
||||
selector:
|
||||
app: redis
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: rng
|
||||
name: rng
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: rng
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rng
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/rng:v0.1
|
||||
name: rng
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: rng
|
||||
name: rng
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: rng
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: webui
|
||||
name: webui
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: webui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: webui
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/webui:v0.1
|
||||
name: webui
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: webui
|
||||
name: webui
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: webui
|
||||
type: NodePort
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: worker
|
||||
name: worker
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: worker
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: worker
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/worker:v0.1
|
||||
name: worker
|
||||
69
k8s/eck-cerebro.yaml
Normal file
69
k8s/eck-cerebro.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: cerebro
|
||||
name: cerebro
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cerebro
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cerebro
|
||||
spec:
|
||||
volumes:
|
||||
- name: conf
|
||||
configMap:
|
||||
name: cerebro
|
||||
containers:
|
||||
- image: lmenezes/cerebro
|
||||
name: cerebro
|
||||
volumeMounts:
|
||||
- name: conf
|
||||
mountPath: /conf
|
||||
args:
|
||||
- -Dconfig.file=/conf/application.conf
|
||||
env:
|
||||
- name: ELASTICSEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: demo-es-elastic-user
|
||||
key: elastic
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: cerebro
|
||||
name: cerebro
|
||||
spec:
|
||||
ports:
|
||||
- port: 9000
|
||||
protocol: TCP
|
||||
targetPort: 9000
|
||||
selector:
|
||||
app: cerebro
|
||||
type: NodePort
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cerebro
|
||||
data:
|
||||
application.conf: |
|
||||
secret = "ki:s:[[@=Ag?QI`W2jMwkY:eqvrJ]JqoJyi2axj3ZvOv^/KavOT4ViJSv?6YY4[N"
|
||||
|
||||
hosts = [
|
||||
{
|
||||
host = "http://demo-es-http.eck-demo.svc.cluster.local:9200"
|
||||
name = "demo"
|
||||
auth = {
|
||||
username = "elastic"
|
||||
password = ${?ELASTICSEARCH_PASSWORD}
|
||||
}
|
||||
}
|
||||
]
|
||||
19
k8s/eck-elasticsearch.yaml
Normal file
19
k8s/eck-elasticsearch.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: elasticsearch.k8s.elastic.co/v1
|
||||
kind: Elasticsearch
|
||||
metadata:
|
||||
name: demo
|
||||
namespace: eck-demo
|
||||
spec:
|
||||
http:
|
||||
tls:
|
||||
selfSignedCertificate:
|
||||
disabled: true
|
||||
nodeSets:
|
||||
- name: default
|
||||
count: 1
|
||||
config:
|
||||
node.data: true
|
||||
node.ingest: true
|
||||
node.master: true
|
||||
node.store.allow_mmap: false
|
||||
version: 7.5.1
|
||||
168
k8s/eck-filebeat.yaml
Normal file
168
k8s/eck-filebeat.yaml
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: filebeat-config
|
||||
namespace: eck-demo
|
||||
labels:
|
||||
k8s-app: filebeat
|
||||
data:
|
||||
filebeat.yml: |-
|
||||
filebeat.inputs:
|
||||
- type: container
|
||||
paths:
|
||||
- /var/log/containers/*.log
|
||||
processors:
|
||||
- add_kubernetes_metadata:
|
||||
host: ${NODE_NAME}
|
||||
matchers:
|
||||
- logs_path:
|
||||
logs_path: "/var/log/containers/"
|
||||
|
||||
# To enable hints based autodiscover, remove `filebeat.inputs` configuration and uncomment this:
|
||||
#filebeat.autodiscover:
|
||||
# providers:
|
||||
# - type: kubernetes
|
||||
# node: ${NODE_NAME}
|
||||
# hints.enabled: true
|
||||
# hints.default_config:
|
||||
# type: container
|
||||
# paths:
|
||||
# - /var/log/containers/*${data.kubernetes.container.id}.log
|
||||
|
||||
processors:
|
||||
- add_cloud_metadata:
|
||||
- add_host_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: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: filebeat
|
||||
namespace: eck-demo
|
||||
labels:
|
||||
k8s-app: filebeat
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: filebeat
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: filebeat
|
||||
spec:
|
||||
serviceAccountName: filebeat
|
||||
terminationGracePeriodSeconds: 30
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
containers:
|
||||
- name: filebeat
|
||||
image: docker.elastic.co/beats/filebeat:7.5.1
|
||||
args: [
|
||||
"-c", "/etc/filebeat.yml",
|
||||
"-e",
|
||||
]
|
||||
env:
|
||||
- name: ELASTICSEARCH_HOST
|
||||
value: demo-es-http
|
||||
- name: ELASTICSEARCH_PORT
|
||||
value: "9200"
|
||||
- name: ELASTICSEARCH_USERNAME
|
||||
value: elastic
|
||||
- name: ELASTICSEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: demo-es-elastic-user
|
||||
key: elastic
|
||||
- name: ELASTIC_CLOUD_ID
|
||||
value:
|
||||
- name: ELASTIC_CLOUD_AUTH
|
||||
value:
|
||||
- name: NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
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: data
|
||||
mountPath: /usr/share/filebeat/data
|
||||
- name: varlibdockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
defaultMode: 0600
|
||||
name: filebeat-config
|
||||
- name: varlibdockercontainers
|
||||
hostPath:
|
||||
path: /var/lib/docker/containers
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
# 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/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: filebeat
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: filebeat
|
||||
namespace: eck-demo
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: filebeat
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
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: eck-demo
|
||||
labels:
|
||||
k8s-app: filebeat
|
||||
---
|
||||
17
k8s/eck-kibana.yaml
Normal file
17
k8s/eck-kibana.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: kibana.k8s.elastic.co/v1
|
||||
kind: Kibana
|
||||
metadata:
|
||||
name: demo
|
||||
spec:
|
||||
version: 7.5.1
|
||||
count: 1
|
||||
elasticsearchRef:
|
||||
name: demo
|
||||
namespace: eck-demo
|
||||
http:
|
||||
service:
|
||||
spec:
|
||||
type: NodePort
|
||||
tls:
|
||||
selfSignedCertificate:
|
||||
disabled: true
|
||||
1802
k8s/eck-operator.yaml
Normal file
1802
k8s/eck-operator.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: fluentd
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
@@ -36,6 +37,7 @@ apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: fluentd
|
||||
namespace: default
|
||||
labels:
|
||||
app: fluentd
|
||||
spec:
|
||||
@@ -95,6 +97,7 @@ metadata:
|
||||
labels:
|
||||
app: elasticsearch
|
||||
name: elasticsearch
|
||||
namespace: default
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -122,6 +125,7 @@ metadata:
|
||||
labels:
|
||||
app: elasticsearch
|
||||
name: elasticsearch
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- port: 9200
|
||||
@@ -137,6 +141,7 @@ metadata:
|
||||
labels:
|
||||
app: kibana
|
||||
name: kibana
|
||||
namespace: default
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -160,6 +165,7 @@ metadata:
|
||||
labels:
|
||||
app: kibana
|
||||
name: kibana
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- port: 5601
|
||||
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
name: haproxy
|
||||
containers:
|
||||
- name: haproxy
|
||||
image: haproxy
|
||||
image: haproxy:1
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /usr/local/etc/haproxy/
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: kibana
|
||||
name: whatever
|
||||
spec:
|
||||
rules:
|
||||
- host: kibana.185.145.251.54.nip.io
|
||||
- host: whatever.A.B.C.D.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: kibana
|
||||
servicePort: 5601
|
||||
serviceName: whatever
|
||||
servicePort: 1234
|
||||
|
||||
@@ -12,19 +12,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# ------------------- Dashboard Secret ------------------- #
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard-certs
|
||||
namespace: kube-system
|
||||
type: Opaque
|
||||
name: kubernetes-dashboard
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Service Account ------------------- #
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -32,62 +25,147 @@ metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
namespace: kubernetes-dashboard
|
||||
|
||||
---
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
ports:
|
||||
- port: 443
|
||||
targetPort: 8443
|
||||
selector:
|
||||
k8s-app: kubernetes-dashboard
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard-certs
|
||||
namespace: kubernetes-dashboard
|
||||
type: Opaque
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard-csrf
|
||||
namespace: kubernetes-dashboard
|
||||
type: Opaque
|
||||
data:
|
||||
csrf: ""
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard-key-holder
|
||||
namespace: kubernetes-dashboard
|
||||
type: Opaque
|
||||
|
||||
---
|
||||
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard-settings
|
||||
namespace: kubernetes-dashboard
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Role & Role Binding ------------------- #
|
||||
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubernetes-dashboard-minimal
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kubernetes-dashboard
|
||||
rules:
|
||||
# Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["create"]
|
||||
# Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["create"]
|
||||
# Allow Dashboard to get, update and delete Dashboard exclusive secrets.
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
|
||||
verbs: ["get", "update", "delete"]
|
||||
# Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
resourceNames: ["kubernetes-dashboard-settings"]
|
||||
verbs: ["get", "update"]
|
||||
# Allow Dashboard to get metrics from heapster.
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
resourceNames: ["heapster"]
|
||||
verbs: ["proxy"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
|
||||
verbs: ["get", "update", "delete"]
|
||||
# Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
resourceNames: ["kubernetes-dashboard-settings"]
|
||||
verbs: ["get", "update"]
|
||||
# Allow Dashboard to get metrics.
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
resourceNames: ["heapster", "dashboard-metrics-scraper"]
|
||||
verbs: ["proxy"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services/proxy"]
|
||||
resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
|
||||
verbs: ["get"]
|
||||
|
||||
---
|
||||
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
rules:
|
||||
# Allow Metrics Scraper to get metrics from the Metrics server
|
||||
- apiGroups: ["metrics.k8s.io"]
|
||||
resources: ["pods", "nodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kubernetes-dashboard-minimal
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kubernetes-dashboard
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kubernetes-dashboard-minimal
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kubernetes-dashboard
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kubernetes-dashboard
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kubernetes-dashboard
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kubernetes-dashboard
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Deployment ------------------- #
|
||||
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
@@ -95,7 +173,7 @@ metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
@@ -108,60 +186,124 @@ spec:
|
||||
k8s-app: kubernetes-dashboard
|
||||
spec:
|
||||
containers:
|
||||
- name: kubernetes-dashboard
|
||||
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
|
||||
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.
|
||||
# - --apiserver-host=http://my-address:port
|
||||
volumeMounts:
|
||||
- name: kubernetes-dashboard-certs
|
||||
mountPath: /certs
|
||||
# Create on-disk volume to store exec logs
|
||||
- mountPath: /tmp
|
||||
name: tmp-volume
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
scheme: HTTPS
|
||||
path: /
|
||||
port: 8443
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 30
|
||||
- name: kubernetes-dashboard
|
||||
image: kubernetesui/dashboard:v2.0.0-rc2
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
protocol: TCP
|
||||
args:
|
||||
- --auto-generate-certificates
|
||||
- --namespace=kubernetes-dashboard
|
||||
# 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.
|
||||
# - --apiserver-host=http://my-address:port
|
||||
- --enable-skip-login
|
||||
volumeMounts:
|
||||
- name: kubernetes-dashboard-certs
|
||||
mountPath: /certs
|
||||
# Create on-disk volume to store exec logs
|
||||
- mountPath: /tmp
|
||||
name: tmp-volume
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
scheme: HTTPS
|
||||
path: /
|
||||
port: 8443
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 30
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsUser: 1001
|
||||
runAsGroup: 2001
|
||||
volumes:
|
||||
- name: kubernetes-dashboard-certs
|
||||
secret:
|
||||
secretName: kubernetes-dashboard-certs
|
||||
- name: tmp-volume
|
||||
emptyDir: {}
|
||||
- name: kubernetes-dashboard-certs
|
||||
secret:
|
||||
secretName: kubernetes-dashboard-certs
|
||||
- name: tmp-volume
|
||||
emptyDir: {}
|
||||
serviceAccountName: kubernetes-dashboard
|
||||
nodeSelector:
|
||||
"beta.kubernetes.io/os": linux
|
||||
# Comment the following tolerations if Dashboard must not be deployed on master
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Service ------------------- #
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
k8s-app: dashboard-metrics-scraper
|
||||
name: dashboard-metrics-scraper
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
ports:
|
||||
- port: 443
|
||||
targetPort: 8443
|
||||
- port: 8000
|
||||
targetPort: 8000
|
||||
selector:
|
||||
k8s-app: kubernetes-dashboard
|
||||
k8s-app: dashboard-metrics-scraper
|
||||
|
||||
---
|
||||
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: dashboard-metrics-scraper
|
||||
name: dashboard-metrics-scraper
|
||||
namespace: kubernetes-dashboard
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: dashboard-metrics-scraper
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: dashboard-metrics-scraper
|
||||
annotations:
|
||||
seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
|
||||
spec:
|
||||
containers:
|
||||
- name: dashboard-metrics-scraper
|
||||
image: kubernetesui/metrics-scraper:v1.0.2
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
scheme: HTTP
|
||||
path: /
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 30
|
||||
volumeMounts:
|
||||
- mountPath: /tmp
|
||||
name: tmp-volume
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsUser: 1001
|
||||
runAsGroup: 2001
|
||||
serviceAccountName: kubernetes-dashboard
|
||||
nodeSelector:
|
||||
"beta.kubernetes.io/os": linux
|
||||
# Comment the following tolerations if Dashboard must not be deployed on master
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
volumes:
|
||||
- name: tmp-volume
|
||||
emptyDir: {}
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -181,10 +323,12 @@ spec:
|
||||
- args:
|
||||
- sh
|
||||
- -c
|
||||
- apk add --no-cache socat && socat TCP-LISTEN:80,fork,reuseaddr OPENSSL:kubernetes-dashboard.kube-system:443,verify=0
|
||||
- apk add --no-cache socat && socat TCP-LISTEN:80,fork,reuseaddr OPENSSL:kubernetes-dashboard.kubernetes-dashboard:443,verify=0
|
||||
image: alpine
|
||||
name: dashboard
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
@@ -199,13 +343,13 @@ spec:
|
||||
selector:
|
||||
app: dashboard
|
||||
type: NodePort
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kubernetes-dashboard
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: insecure-dashboard
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
@@ -213,4 +357,4 @@ roleRef:
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
namespace: kubernetes-dashboard
|
||||
|
||||
8
k8s/nginx-1-without-volume.yaml
Normal file
8
k8s/nginx-1-without-volume.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-without-volume
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
13
k8s/nginx-2-with-volume.yaml
Normal file
13
k8s/nginx-2-with-volume.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-with-volume
|
||||
spec:
|
||||
volumes:
|
||||
- name: www
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /usr/share/nginx/html/
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-with-volume
|
||||
name: nginx-with-git
|
||||
spec:
|
||||
volumes:
|
||||
- name: www
|
||||
20
k8s/nginx-4-with-init.yaml
Normal file
20
k8s/nginx-4-with-init.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-with-init
|
||||
spec:
|
||||
volumes:
|
||||
- name: www
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /usr/share/nginx/html/
|
||||
initContainers:
|
||||
- name: git
|
||||
image: alpine
|
||||
command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ]
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /www/
|
||||
@@ -1,51 +1,54 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: consul
|
||||
name: persistentconsul
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ pods ]
|
||||
verbs: [ get, list ]
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: consul
|
||||
name: persistentconsul
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: consul
|
||||
kind: ClusterRole
|
||||
name: persistentconsul
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: consul
|
||||
namespace: orange
|
||||
name: persistentconsul
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: consul
|
||||
name: persistentconsul
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: consul
|
||||
name: persistentconsul
|
||||
spec:
|
||||
ports:
|
||||
- port: 8500
|
||||
name: http
|
||||
selector:
|
||||
app: consul
|
||||
app: persistentconsul
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: consul
|
||||
name: persistentconsul
|
||||
spec:
|
||||
serviceName: consul
|
||||
serviceName: persistentconsul
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: consul
|
||||
app: persistentconsul
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
@@ -58,9 +61,9 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: consul
|
||||
app: persistentconsul
|
||||
spec:
|
||||
serviceAccountName: consul
|
||||
serviceAccountName: persistentconsul
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
@@ -69,19 +72,19 @@ spec:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- consul
|
||||
- persistentconsul
|
||||
topologyKey: kubernetes.io/hostname
|
||||
terminationGracePeriodSeconds: 10
|
||||
containers:
|
||||
- name: consul
|
||||
image: "consul:1.5"
|
||||
image: "consul:1.6"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /consul/data
|
||||
args:
|
||||
- "agent"
|
||||
- "-bootstrap-expect=3"
|
||||
- "-retry-join=provider=k8s namespace=orange label_selector=\"app=consul\""
|
||||
- "-retry-join=provider=k8s label_selector=\"app=persistentconsul\""
|
||||
- "-client=0.0.0.0"
|
||||
- "-data-dir=/consul/data"
|
||||
- "-server"
|
||||
|
||||
@@ -1,5 +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
|
||||
# SOURCE: https://install.portworx.com/?mc=false&kbver=1.17.1&b=true&s=%2Fdev%2Floop4&j=auto&c=px-workshop&stork=true&csi=true&lh=true&st=k8s
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
@@ -11,7 +10,7 @@ metadata:
|
||||
spec:
|
||||
selector:
|
||||
name: portworx
|
||||
type: NodePort
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: px-api
|
||||
protocol: TCP
|
||||
@@ -51,6 +50,165 @@ spec:
|
||||
shortNames:
|
||||
- vps
|
||||
- vp
|
||||
preserveUnknownFields: false
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
description: The desired spec of the volume placement strategy
|
||||
properties:
|
||||
replicaAffinity:
|
||||
type: array
|
||||
description: Allows you to specify a rule which creates an affinity for replicas within a volume
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
enforcement:
|
||||
type: string
|
||||
enum:
|
||||
- required
|
||||
- preferred
|
||||
description: Specifies if the given rule is required (hard) or preferred (soft)
|
||||
topologyKey:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Key for the node label that the system uses to denote a topology domain. The key can be for any node label that is present on the Kubernetes node.
|
||||
matchExpressions:
|
||||
description: Expression to use for the replica affinity rule
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
minLength: 1
|
||||
operator:
|
||||
type: string
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Lt
|
||||
- Gt
|
||||
description: The logical operator to use for comparing the key and values in the match expression
|
||||
values:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
replicaAntiAffinity:
|
||||
type: array
|
||||
description: Allows you to specify a rule that creates an anti-affinity for replicas within a volume
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
enforcement:
|
||||
type: string
|
||||
enum:
|
||||
- required
|
||||
- preferred
|
||||
description: Specifies if the given rule is required (hard) or preferred (soft)
|
||||
topologyKey:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Key for the node label that the system uses to denote a topology domain. The key can be for any node label that is present on the Kubernetes node.
|
||||
required:
|
||||
- topologyKey
|
||||
volumeAffinity:
|
||||
type: array
|
||||
description: Allows you to colocate volumes by specifying rules that place replicas of a volume together with those of another volume for which the specified labels match
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
enforcement:
|
||||
type: string
|
||||
enum:
|
||||
- required
|
||||
- preferred
|
||||
description: Specifies if the given rule is required (hard) or preferred (soft)
|
||||
topologyKey:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Key for the node label that the system uses to denote a topology domain. The key can be for any node label that is present on the Kubernetes node.
|
||||
matchExpressions:
|
||||
description: Expression to use for the volume affinity rule
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
minLength: 1
|
||||
operator:
|
||||
type: string
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Lt
|
||||
- Gt
|
||||
description: The logical operator to use for comparing the key and values in the match expression
|
||||
values:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
required:
|
||||
- matchExpressions
|
||||
volumeAntiAffinity:
|
||||
type: array
|
||||
description: Allows you to specify dissociation rules between 2 or more volumes that match the given labels
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
enforcement:
|
||||
type: string
|
||||
enum:
|
||||
- required
|
||||
- preferred
|
||||
description: Specifies if the given rule is required (hard) or preferred (soft)
|
||||
topologyKey:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Key for the node label that the system uses to denote a topology domain. The key can be for any node label that is present on the Kubernetes node.
|
||||
matchExpressions:
|
||||
description: Expression to use for the volume anti affinity rule
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
minLength: 1
|
||||
operator:
|
||||
type: string
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Lt
|
||||
- Gt
|
||||
description: The logical operator to use for comparing the key and values in the match expression
|
||||
values:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
required:
|
||||
- matchExpressions
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -85,6 +243,13 @@ rules:
|
||||
- apiGroups: ["portworx.io"]
|
||||
resources: ["volumeplacementstrategies"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: ["stork.libopenstorage.org"]
|
||||
resources: ["backuplocations"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["create"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -128,14 +293,19 @@ roleRef:
|
||||
name: px-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: portworx
|
||||
namespace: kube-system
|
||||
labels:
|
||||
name: portworx
|
||||
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"
|
||||
portworx.com/install-source: "https://install.portworx.com/?mc=false&kbver=1.17.1&b=true&s=%2Fdev%2Floop4&j=auto&c=px-workshop&stork=true&csi=true&lh=true&st=k8s"
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: portworx
|
||||
minReadySeconds: 0
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
@@ -159,28 +329,20 @@ spec:
|
||||
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
|
||||
image: portworx/oci-monitor:2.3.2
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
["-c", "px-workshop", "-s", "/dev/loop4", "-secret_type", "k8s", "-b",
|
||||
["-c", "px-workshop", "-s", "/dev/loop4", "-secret_type", "k8s", "-j", "auto", "-b",
|
||||
"-x", "kubernetes"]
|
||||
env:
|
||||
- name: "AUTO_NODE_RECOVERY_TIMEOUT_IN_SECS"
|
||||
value: "1500"
|
||||
- name: "PX_TEMPLATE_VERSION"
|
||||
value: "v4"
|
||||
- name: CSI_ENDPOINT
|
||||
value: unix:///var/lib/kubelet/plugins/pxd.portworx.com/csi.sock
|
||||
|
||||
livenessProbe:
|
||||
periodSeconds: 30
|
||||
@@ -211,6 +373,10 @@ spec:
|
||||
mountPath: /etc/crictl.yaml
|
||||
- name: etcpwx
|
||||
mountPath: /etc/pwx
|
||||
- name: dev
|
||||
mountPath: /dev
|
||||
- name: csi-driver-path
|
||||
mountPath: /var/lib/kubelet/plugins/pxd.portworx.com
|
||||
- name: optpwx
|
||||
mountPath: /opt/pwx
|
||||
- name: procmount
|
||||
@@ -225,6 +391,27 @@ spec:
|
||||
readOnly: true
|
||||
- name: dbusmount
|
||||
mountPath: /var/run/dbus
|
||||
- name: csi-node-driver-registrar
|
||||
image: quay.io/k8scsi/csi-node-driver-registrar:v1.1.0
|
||||
args:
|
||||
- "--v=5"
|
||||
- "--csi-address=$(ADDRESS)"
|
||||
- "--kubelet-registration-path=/var/lib/kubelet/plugins/pxd.portworx.com/csi.sock"
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ADDRESS
|
||||
value: /csi/csi.sock
|
||||
- name: KUBE_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: csi-driver-path
|
||||
mountPath: /csi
|
||||
- name: registration-dir
|
||||
mountPath: /registration
|
||||
restartPolicy: Always
|
||||
serviceAccountName: px-account
|
||||
volumes:
|
||||
@@ -247,6 +434,17 @@ spec:
|
||||
- name: etcpwx
|
||||
hostPath:
|
||||
path: /etc/pwx
|
||||
- name: dev
|
||||
hostPath:
|
||||
path: /dev
|
||||
- name: registration-dir
|
||||
hostPath:
|
||||
path: /var/lib/kubelet/plugins_registry
|
||||
type: DirectoryOrCreate
|
||||
- name: csi-driver-path
|
||||
hostPath:
|
||||
path: /var/lib/kubelet/plugins/pxd.portworx.com
|
||||
type: DirectoryOrCreate
|
||||
- name: optpwx
|
||||
hostPath:
|
||||
path: /opt/pwx
|
||||
@@ -266,6 +464,172 @@ spec:
|
||||
hostPath:
|
||||
path: /var/run/dbus
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: px-csi-account
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: px-csi-role
|
||||
rules:
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["podsecuritypolicies"]
|
||||
resourceNames: ["privileged"]
|
||||
verbs: ["use"]
|
||||
- apiGroups: ["apiextensions.k8s.io"]
|
||||
resources: ["customresourcedefinitions"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumes"]
|
||||
verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims/status"]
|
||||
verbs: ["update", "patch"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["storageclasses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["volumeattachments"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["list", "watch", "create", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: ["snapshot.storage.k8s.io"]
|
||||
resources: ["volumesnapshots", "volumesnapshotcontents", "volumesnapshotclasses", "volumesnapshots/status"]
|
||||
verbs: ["create", "get", "list", "watch", "update", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["csinodes"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["csi.storage.k8s.io"]
|
||||
resources: ["csidrivers"]
|
||||
verbs: ["create", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["get", "watch", "list", "delete", "update", "create"]
|
||||
- apiGroups: ["coordination.k8s.io"]
|
||||
resources: ["leases"]
|
||||
verbs: ["*"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: px-csi-role-binding
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: px-csi-account
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: px-csi-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: px-csi-service
|
||||
namespace: kube-system
|
||||
spec:
|
||||
clusterIP: None
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: px-csi-ext
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: px-csi-driver
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: px-csi-driver
|
||||
spec:
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: px/enabled
|
||||
operator: NotIn
|
||||
values:
|
||||
- "false"
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: DoesNotExist
|
||||
serviceAccount: px-csi-account
|
||||
containers:
|
||||
- name: csi-external-provisioner
|
||||
imagePullPolicy: Always
|
||||
image: quay.io/openstorage/csi-provisioner:v1.4.0-1
|
||||
args:
|
||||
- "--v=5"
|
||||
- "--provisioner=pxd.portworx.com"
|
||||
- "--csi-address=$(ADDRESS)"
|
||||
- "--enable-leader-election"
|
||||
- "--leader-election-type=leases"
|
||||
env:
|
||||
- name: ADDRESS
|
||||
value: /csi/csi.sock
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: socket-dir
|
||||
mountPath: /csi
|
||||
- name: csi-snapshotter
|
||||
image: quay.io/k8scsi/csi-snapshotter:v2.0.0
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--v=3"
|
||||
- "--csi-address=$(ADDRESS)"
|
||||
- "--leader-election=true"
|
||||
env:
|
||||
- name: ADDRESS
|
||||
value: /csi/csi.sock
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: socket-dir
|
||||
mountPath: /csi
|
||||
- name: csi-resizer
|
||||
imagePullPolicy: Always
|
||||
image: quay.io/k8scsi/csi-resizer:v0.3.0
|
||||
args:
|
||||
- "--v=5"
|
||||
- "--csi-address=$(ADDRESS)"
|
||||
- "--leader-election=true"
|
||||
env:
|
||||
- name: ADDRESS
|
||||
value: /csi/csi.sock
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: socket-dir
|
||||
mountPath: /csi
|
||||
volumes:
|
||||
- name: socket-dir
|
||||
hostPath:
|
||||
path: /var/lib/kubelet/plugins/pxd.portworx.com
|
||||
type: DirectoryOrCreate
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
@@ -276,7 +640,7 @@ metadata:
|
||||
spec:
|
||||
selector:
|
||||
name: portworx-api
|
||||
type: NodePort
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: px-api
|
||||
protocol: TCP
|
||||
@@ -291,12 +655,17 @@ spec:
|
||||
port: 9021
|
||||
targetPort: 9021
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: portworx-api
|
||||
namespace: kube-system
|
||||
labels:
|
||||
name: portworx-api
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: portworx-api
|
||||
minReadySeconds: 0
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
@@ -332,8 +701,14 @@ spec:
|
||||
port: 9001
|
||||
restartPolicy: Always
|
||||
serviceAccountName: px-account
|
||||
|
||||
|
||||
---
|
||||
apiVersion: storage.k8s.io/v1beta1
|
||||
kind: CSIDriver
|
||||
metadata:
|
||||
name: pxd.portworx.com
|
||||
spec:
|
||||
attachRequired: false
|
||||
podInfoOnMount: false
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@@ -369,48 +744,9 @@ apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: stork-role
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/exec"]
|
||||
verbs: ["get", "list", "delete", "create", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumes"]
|
||||
verbs: ["get", "list", "watch", "create", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["storageclasses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- 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"]
|
||||
- apiGroups: ["volumesnapshot.external-storage.k8s.io"]
|
||||
resources: ["volumesnapshots", "volumesnapshotdatas"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["get", "create", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["*"]
|
||||
resources: ["deployments", "deployments/extensions"]
|
||||
verbs: ["list", "get", "watch", "patch", "update", "initialize"]
|
||||
- apiGroups: ["*"]
|
||||
resources: ["statefulsets", "statefulsets/extensions"]
|
||||
verbs: ["list", "get", "watch", "patch", "update", "initialize"]
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs: ["list", "get"]
|
||||
verbs: ["*"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -438,7 +774,7 @@ spec:
|
||||
port: 8099
|
||||
targetPort: 8099
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
@@ -448,6 +784,9 @@ metadata:
|
||||
name: stork
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: stork
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
@@ -470,7 +809,7 @@ spec:
|
||||
- --leader-elect=true
|
||||
- --health-monitor-interval=120
|
||||
imagePullPolicy: Always
|
||||
image: openstorage/stork:2.2.4
|
||||
image: openstorage/stork:2.3.1
|
||||
env:
|
||||
- name: "PX_SERVICE_NAME"
|
||||
value: "portworx-api"
|
||||
@@ -513,8 +852,8 @@ rules:
|
||||
verbs: ["get", "create", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: [""]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["", "events.k8s.io"]
|
||||
resources: ["events"]
|
||||
verbs: ["create", "patch", "update"]
|
||||
- apiGroups: [""]
|
||||
@@ -549,8 +888,11 @@ rules:
|
||||
resources: ["persistentvolumeclaims", "persistentvolumes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["storageclasses"]
|
||||
resources: ["storageclasses", "csinodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["coordination.k8s.io"]
|
||||
resources: ["leases"]
|
||||
verbs: ["create", "update", "get", "list", "watch"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -565,7 +907,7 @@ roleRef:
|
||||
name: stork-scheduler-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
@@ -575,12 +917,16 @@ metadata:
|
||||
name: stork-scheduler
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: stork-scheduler
|
||||
replicas: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: scheduler
|
||||
tier: control-plane
|
||||
name: stork-scheduler
|
||||
name: stork-scheduler
|
||||
spec:
|
||||
containers:
|
||||
@@ -592,7 +938,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.17.1
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
@@ -694,7 +1040,7 @@ spec:
|
||||
selector:
|
||||
tier: px-web-console
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: px-lighthouse
|
||||
@@ -702,6 +1048,9 @@ metadata:
|
||||
labels:
|
||||
tier: px-web-console
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: px-web-console
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
@@ -718,7 +1067,7 @@ spec:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: portworx/lh-config-sync:0.4
|
||||
image: portworx/lh-config-sync:2.0.5
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "init"
|
||||
@@ -727,7 +1076,7 @@ spec:
|
||||
mountPath: /config/lh
|
||||
containers:
|
||||
- name: px-lighthouse
|
||||
image: portworx/px-lighthouse:2.0.4
|
||||
image: portworx/px-lighthouse:2.0.6
|
||||
imagePullPolicy: Always
|
||||
args: [ "-kubernetes", "true" ]
|
||||
ports:
|
||||
@@ -737,7 +1086,7 @@ spec:
|
||||
- name: config
|
||||
mountPath: /config/lh
|
||||
- name: config-sync
|
||||
image: portworx/lh-config-sync:0.4
|
||||
image: portworx/lh-config-sync:2.0.5
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "sync"
|
||||
@@ -745,9 +1094,23 @@ spec:
|
||||
- name: config
|
||||
mountPath: /config/lh
|
||||
- name: stork-connector
|
||||
image: portworx/lh-stork-connector:0.2
|
||||
image: portworx/lh-stork-connector:2.0.5
|
||||
imagePullPolicy: Always
|
||||
serviceAccountName: px-lh-account
|
||||
volumes:
|
||||
- name: config
|
||||
emptyDir: {}
|
||||
---
|
||||
# That one is an extra.
|
||||
# Create a default Storage Class to simplify Portworx setup.
|
||||
kind: StorageClass
|
||||
apiVersion: storage.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: portworx-replicated
|
||||
annotations:
|
||||
storageclass.kubernetes.io/is-default-class: "true"
|
||||
provisioner: kubernetes.io/portworx-volume
|
||||
parameters:
|
||||
repl: "2"
|
||||
priority_io: "high"
|
||||
|
||||
|
||||
@@ -12,7 +12,14 @@ spec:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
schedulerName: stork
|
||||
#schedulerName: stork
|
||||
initContainers:
|
||||
- name: rmdir
|
||||
image: alpine
|
||||
volumeMounts:
|
||||
- mountPath: /vol
|
||||
name: postgres
|
||||
command: ["sh", "-c", "if [ -d /vol/lost+found ]; then rmdir /vol/lost+found; fi"]
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:11
|
||||
|
||||
@@ -7,9 +7,9 @@ workshop.
|
||||
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
Virtualbox, Vagrant and Ansible
|
||||
|
||||
|
||||
- Virtualbox: https://www.virtualbox.org/wiki/Downloads
|
||||
|
||||
- Vagrant: https://www.vagrantup.com/downloads.html
|
||||
@@ -25,7 +25,7 @@ Virtualbox, Vagrant and Ansible
|
||||
|
||||
$ git clone --recursive https://github.com/ansible/ansible.git
|
||||
$ cd ansible
|
||||
$ git checkout stable-2.0.0.1
|
||||
$ git checkout stable-{{ getStableVersionFromAnsibleProject }}
|
||||
$ git submodule update
|
||||
|
||||
- source the setup script to make Ansible available on this terminal session:
|
||||
@@ -38,6 +38,7 @@ Virtualbox, Vagrant and Ansible
|
||||
|
||||
|
||||
## 2. Preparing the environment
|
||||
Change into directory that has your Vagrantfile
|
||||
|
||||
Run the following commands:
|
||||
|
||||
@@ -66,6 +67,14 @@ will reflect inside the instance.
|
||||
|
||||
- Depending on the Vagrant version, `sudo apt-get install bsdtar` may be needed
|
||||
|
||||
- If you get an error like "no Vagrant file found" or you have a file but "cannot open base box" when running `vagrant up`,
|
||||
chances are good you not in the correct directory.
|
||||
Make sure you are in sub directory named "prepare-local". It has all the config files required by ansible, vagrant and virtualbox
|
||||
|
||||
- If you are using Python 3.7, running the ansible-playbook provisioning, see an error like "SyntaxError: invalid syntax" and it mentions
|
||||
the word "async", you need to upgrade your Ansible version to 2.6 or higher to resolve the keyword conflict.
|
||||
https://github.com/ansible/ansible/issues/42105
|
||||
|
||||
- If you get strange Ansible errors about dependencies, try to check your pip
|
||||
version with `pip --version`. The current version is 8.1.1. If your pip is
|
||||
older than this, upgrade it with `sudo pip install --upgrade pip`, restart
|
||||
|
||||
@@ -10,15 +10,21 @@ These tools can help you to create VMs on:
|
||||
|
||||
- [Docker](https://docs.docker.com/engine/installation/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`) - the configuration scripts require this
|
||||
- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`)
|
||||
|
||||
Depending on the infrastructure that you want to use, you also need to install
|
||||
the Azure CLI, the AWS CLI, or terraform (for OpenStack deployment).
|
||||
|
||||
And if you want to generate printable cards:
|
||||
|
||||
- [pyyaml](https://pypi.python.org/pypi/PyYAML) (on a Mac: `brew install pyyaml`)
|
||||
- [jinja2](https://pypi.python.org/pypi/Jinja2) (on a Mac: `brew install jinja2`)
|
||||
- [pyyaml](https://pypi.python.org/pypi/PyYAML)
|
||||
- [jinja2](https://pypi.python.org/pypi/Jinja2)
|
||||
|
||||
You can install them with pip (perhaps with `pip install --user`, or even use `virtualenv` if that's your thing).
|
||||
|
||||
These require Python 3. If you are on a Mac, see below for specific instructions on setting up
|
||||
Python 3 to be the default Python on a Mac. In particular, if you installed `mosh`, Homebrew
|
||||
may have changed your default Python to Python 2.
|
||||
|
||||
## General Workflow
|
||||
|
||||
@@ -256,3 +262,32 @@ If you don't have `wkhtmltopdf` installed, you will get a warning that it is a m
|
||||
|
||||
- Don't write to bash history in system() in postprep
|
||||
- compose, etc version inconsistent (int vs str)
|
||||
|
||||
## Making sure Python3 is the default (Mac only)
|
||||
|
||||
Check the `/usr/local/bin/python` symlink. It should be pointing to
|
||||
`/usr/local/Cellar/python/3`-something. If it isn't, follow these
|
||||
instructions.
|
||||
|
||||
1) Verify that Python 3 is installed.
|
||||
|
||||
```
|
||||
ls -la /usr/local/Cellar/Python
|
||||
```
|
||||
|
||||
You should see one or more versions of Python 3. If you don't,
|
||||
install it with `brew install python`.
|
||||
|
||||
2) Verify that `python` points to Python3.
|
||||
|
||||
```
|
||||
ls -la /usr/local/bin/python
|
||||
```
|
||||
|
||||
If this points to `/usr/local/Cellar/python@2`, then we'll need to change it.
|
||||
|
||||
```
|
||||
rm /usr/local/bin/python
|
||||
ln -s /usr/local/Cellar/Python/xxxx /usr/local/bin/python
|
||||
# where xxxx is the most recent Python 3 version you saw above
|
||||
```
|
||||
|
||||
10
prepare-vms/e2e.sh
Executable file
10
prepare-vms/e2e.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
TAG=$(./workshopctl maketag)
|
||||
./workshopctl start --settings settings/jerome.yaml --infra infra/aws-eu-central-1 --tag $TAG
|
||||
./workshopctl deploy $TAG
|
||||
./workshopctl kube $TAG
|
||||
./workshopctl helmprom $TAG
|
||||
while ! ./workshopctl kubetest $TAG; do sleep 1; done
|
||||
./workshopctl tmux $TAG
|
||||
echo ./workshopctl stop $TAG
|
||||
@@ -127,11 +127,11 @@ _cmd_kubebins() {
|
||||
set -e
|
||||
cd /usr/local/bin
|
||||
if ! [ -x etcd ]; then
|
||||
curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz \
|
||||
curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.15/etcd-v3.3.15-linux-amd64.tar.gz \
|
||||
| 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.16.2/kubernetes-server-linux-amd64.tar.gz \
|
||||
| sudo tar --strip-components=3 -zx kubernetes/server/bin/hyperkube
|
||||
fi
|
||||
if ! [ -x kubelet ]; then
|
||||
@@ -143,7 +143,7 @@ _cmd_kubebins() {
|
||||
sudo mkdir -p /opt/cni/bin
|
||||
cd /opt/cni/bin
|
||||
if ! [ -x bridge ]; then
|
||||
curl -L https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-amd64-v0.7.5.tgz \
|
||||
curl -L https://github.com/containernetworking/plugins/releases/download/v0.7.6/cni-plugins-amd64-v0.7.6.tgz \
|
||||
| sudo tar -zx
|
||||
fi
|
||||
"
|
||||
@@ -242,7 +242,7 @@ EOF"
|
||||
# Install helm
|
||||
pssh "
|
||||
if [ ! -x /usr/local/bin/helm ]; then
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | sudo bash &&
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get-helm-3 | sudo bash &&
|
||||
helm completion bash | sudo tee /etc/bash_completion.d/helm
|
||||
fi"
|
||||
|
||||
@@ -323,6 +323,15 @@ _cmd_listall() {
|
||||
done
|
||||
}
|
||||
|
||||
_cmd maketag "Generate a quasi-unique tag for a group of instances"
|
||||
_cmd_maketag() {
|
||||
if [ -z $USER ]; then
|
||||
export USER=anonymous
|
||||
fi
|
||||
MS=$(($(date +%N)/1000000))
|
||||
date +%Y-%m-%d-%H-%M-$MS-$USER
|
||||
}
|
||||
|
||||
_cmd ping "Ping VMs in a given tag, to check that they have network access"
|
||||
_cmd_ping() {
|
||||
TAG=$1
|
||||
@@ -362,6 +371,16 @@ _cmd_opensg() {
|
||||
infra_opensg
|
||||
}
|
||||
|
||||
_cmd portworx "Prepare the nodes for Portworx deployment"
|
||||
_cmd_portworx() {
|
||||
TAG=$1
|
||||
need_tag
|
||||
|
||||
pssh "
|
||||
sudo truncate --size 10G /portworx.blk &&
|
||||
sudo losetup /dev/loop4 /portworx.blk"
|
||||
}
|
||||
|
||||
_cmd disableaddrchecks "Disable source/destination IP address checks"
|
||||
_cmd_disableaddrchecks() {
|
||||
TAG=$1
|
||||
@@ -455,7 +474,7 @@ _cmd_start() {
|
||||
need_infra $INFRA
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG=$(make_tag)
|
||||
TAG=$(_cmd_maketag)
|
||||
fi
|
||||
mkdir -p tags/$TAG
|
||||
ln -s ../../$INFRA tags/$TAG/infra.sh
|
||||
@@ -517,20 +536,24 @@ _cmd_test() {
|
||||
test_tag
|
||||
}
|
||||
|
||||
_cmd tmux "Log into the first node and start a tmux server"
|
||||
_cmd_tmux() {
|
||||
TAG=$1
|
||||
need_tag
|
||||
IP=$(head -1 tags/$TAG/ips.txt)
|
||||
info "Opening ssh+tmux with $IP"
|
||||
rm -f /tmp/tmux-$UID/default
|
||||
ssh -t -L /tmp/tmux-$UID/default:/tmp/tmux-1001/default docker@$IP tmux new-session -As 0
|
||||
}
|
||||
|
||||
_cmd helmprom "Install Helm and Prometheus"
|
||||
_cmd_helmprom() {
|
||||
TAG=$1
|
||||
need_tag
|
||||
pssh "
|
||||
if i_am_first_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
|
||||
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 \
|
||||
sudo -u docker -H helm repo add stable https://kubernetes-charts.storage.googleapis.com/
|
||||
sudo -u docker -H helm install prometheus stable/prometheus \
|
||||
--namespace kube-system \
|
||||
--set server.service.type=NodePort \
|
||||
--set server.service.nodePort=30090 \
|
||||
@@ -717,10 +740,3 @@ sync_keys() {
|
||||
info "Using existing key $AWS_KEY_NAME."
|
||||
fi
|
||||
}
|
||||
|
||||
make_tag() {
|
||||
if [ -z $USER ]; then
|
||||
export USER=anonymous
|
||||
fi
|
||||
date +%Y-%m-%d-%H-%M-$USER
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ system("sudo sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /e
|
||||
system("sudo service ssh restart")
|
||||
system("sudo apt-get -q update")
|
||||
system("sudo apt-get -qy install git jq")
|
||||
system("sudo apt-get -qy install emacs-nox joe")
|
||||
|
||||
#######################
|
||||
### DOCKER INSTALLS ###
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.9
|
||||
RUN apk add --no-cache entr py-pip git
|
||||
FROM alpine:3.11
|
||||
RUN apk add --no-cache entr py3-pip git zip
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# Uncomment and/or edit one of the the following lines if necessary.
|
||||
#/ /kube-halfday.yml.html 200
|
||||
#/ /kube-fullday.yml.html 200
|
||||
/ /kube-twodays.yml.html 200!
|
||||
/ /kube.yml.html 200!
|
||||
|
||||
# And this allows to do "git clone https://container.training".
|
||||
/info/refs service=git-upload-pack https://github.com/jpetazzo/container.training/info/refs?service=git-upload-pack
|
||||
|
||||
/dockermastery https://www.udemy.com/course/docker-mastery/?referralCode=1410924A733D33635CCB
|
||||
/kubernetesmastery https://www.udemy.com/course/kubernetesmastery/?referralCode=7E09090AF9B79E6C283F
|
||||
|
||||
@@ -26,9 +26,10 @@ IPADDR = None
|
||||
class State(object):
|
||||
|
||||
def __init__(self):
|
||||
self.clipboard = ""
|
||||
self.interactive = True
|
||||
self.verify_status = False
|
||||
self.simulate_type = True
|
||||
self.verify_status = True
|
||||
self.simulate_type = False
|
||||
self.switch_desktop = False
|
||||
self.sync_slides = False
|
||||
self.open_links = False
|
||||
@@ -38,6 +39,7 @@ class State(object):
|
||||
|
||||
def load(self):
|
||||
data = yaml.load(open("state.yaml"))
|
||||
self.clipboard = str(data["clipboard"])
|
||||
self.interactive = bool(data["interactive"])
|
||||
self.verify_status = bool(data["verify_status"])
|
||||
self.simulate_type = bool(data["simulate_type"])
|
||||
@@ -51,6 +53,7 @@ class State(object):
|
||||
def save(self):
|
||||
with open("state.yaml", "w") as f:
|
||||
yaml.dump(dict(
|
||||
clipboard=self.clipboard,
|
||||
interactive=self.interactive,
|
||||
verify_status=self.verify_status,
|
||||
simulate_type=self.simulate_type,
|
||||
@@ -66,6 +69,8 @@ class State(object):
|
||||
state = State()
|
||||
|
||||
|
||||
outfile = open("autopilot.log", "w")
|
||||
|
||||
def hrule():
|
||||
return "="*int(subprocess.check_output(["tput", "cols"]))
|
||||
|
||||
@@ -85,9 +90,11 @@ class Snippet(object):
|
||||
# On single-line snippets, the data follows the method immediately
|
||||
if '\n' in content:
|
||||
self.method, self.data = content.split('\n', 1)
|
||||
else:
|
||||
self.data = self.data.strip()
|
||||
elif ' ' in content:
|
||||
self.method, self.data = content.split(' ', 1)
|
||||
self.data = self.data.strip()
|
||||
else:
|
||||
self.method, self.data = content, None
|
||||
self.next = None
|
||||
|
||||
def __str__(self):
|
||||
@@ -186,7 +193,7 @@ def wait_for_prompt():
|
||||
if last_line == "$":
|
||||
# This is a perfect opportunity to grab the node's IP address
|
||||
global IPADDR
|
||||
IPADDR = re.findall("^\[(.*)\]", output, re.MULTILINE)[-1]
|
||||
IPADDR = re.findall("\[(.*)\]", output, re.MULTILINE)[-1]
|
||||
return
|
||||
# When we are in an alpine container, the prompt will be "/ #"
|
||||
if last_line == "/ #":
|
||||
@@ -235,6 +242,8 @@ tmux
|
||||
|
||||
rm -f /tmp/tmux-{uid}/default && ssh -t -L /tmp/tmux-{uid}/default:/tmp/tmux-1001/default docker@{ipaddr} tmux new-session -As 0
|
||||
|
||||
(Or use workshopctl tmux)
|
||||
|
||||
3. If you cannot control a remote tmux:
|
||||
|
||||
tmux new-session ssh docker@{ipaddr}
|
||||
@@ -259,26 +268,11 @@ for slide in re.split("\n---?\n", content):
|
||||
slide_classes = slide_classes[0].split(",")
|
||||
slide_classes = [c.strip() for c in slide_classes]
|
||||
if excluded_classes & set(slide_classes):
|
||||
logging.info("Skipping excluded slide.")
|
||||
logging.debug("Skipping excluded slide.")
|
||||
continue
|
||||
slides.append(Slide(slide))
|
||||
|
||||
|
||||
def send_keys(data):
|
||||
if state.simulate_type and data[0] != '^':
|
||||
for key in data:
|
||||
if key == ";":
|
||||
key = "\\;"
|
||||
if key == "\n":
|
||||
if interruptible_sleep(1): return
|
||||
subprocess.check_call(["tmux", "send-keys", key])
|
||||
if interruptible_sleep(0.15*random.random()): return
|
||||
if key == "\n":
|
||||
if interruptible_sleep(1): return
|
||||
else:
|
||||
subprocess.check_call(["tmux", "send-keys", data])
|
||||
|
||||
|
||||
def capture_pane():
|
||||
return subprocess.check_output(["tmux", "capture-pane", "-p"]).decode('utf-8')
|
||||
|
||||
@@ -288,7 +282,7 @@ setup_tmux_and_ssh()
|
||||
|
||||
try:
|
||||
state.load()
|
||||
logging.info("Successfully loaded state from file.")
|
||||
logging.debug("Successfully loaded state from file.")
|
||||
# Let's override the starting state, so that when an error occurs,
|
||||
# we can restart the auto-tester and then single-step or debug.
|
||||
# (Instead of running again through the same issue immediately.)
|
||||
@@ -297,6 +291,7 @@ except Exception as e:
|
||||
logging.exception("Could not load state from file.")
|
||||
logging.warning("Using default values.")
|
||||
|
||||
|
||||
def move_forward():
|
||||
state.snippet += 1
|
||||
if state.snippet > len(slides[state.slide].snippets):
|
||||
@@ -320,10 +315,147 @@ def check_bounds():
|
||||
state.slide = len(slides)-1
|
||||
|
||||
|
||||
##########################################################
|
||||
# All functions starting with action_ correspond to the
|
||||
# code to be executed when seeing ```foo``` blocks in the
|
||||
# input. ```foo``` would call action_foo(state, snippet).
|
||||
##########################################################
|
||||
|
||||
|
||||
def send_keys(keys):
|
||||
subprocess.check_call(["tmux", "send-keys", keys])
|
||||
|
||||
# Send a single key.
|
||||
# Useful for special keys, e.g. tmux interprets these strings:
|
||||
# ^C (and all other sequences starting with a caret)
|
||||
# Space
|
||||
# ... and many others (check tmux manpage for details).
|
||||
def action_key(state, snippet):
|
||||
send_keys(snippet.data)
|
||||
|
||||
|
||||
# Send multiple keys.
|
||||
# If keystroke simulation is off, all keys are sent at once.
|
||||
# If keystroke simulation is on, keys are sent one by one, with a delay between them.
|
||||
def action_keys(state, snippet, keys=None):
|
||||
if keys is None:
|
||||
keys = snippet.data
|
||||
if not state.simulate_type:
|
||||
send_keys(keys)
|
||||
else:
|
||||
for key in keys:
|
||||
if key == ";":
|
||||
key = "\\;"
|
||||
if key == "\n":
|
||||
if interruptible_sleep(1): return
|
||||
send_keys(key)
|
||||
if interruptible_sleep(0.15*random.random()): return
|
||||
if key == "\n":
|
||||
if interruptible_sleep(1): return
|
||||
|
||||
|
||||
def action_hide(state, snippet):
|
||||
if state.run_hidden:
|
||||
action_bash(state, snippet)
|
||||
|
||||
|
||||
def action_bash(state, snippet):
|
||||
data = snippet.data
|
||||
# Make sure that we're ready
|
||||
wait_for_prompt()
|
||||
# Strip leading spaces
|
||||
data = re.sub("\n +", "\n", data)
|
||||
# Remove backticks (they are used to highlight sections)
|
||||
data = data.replace('`', '')
|
||||
# Add "RETURN" at the end of the command :)
|
||||
data += "\n"
|
||||
# Send command
|
||||
action_keys(state, snippet, data)
|
||||
# Force a short sleep to avoid race condition
|
||||
time.sleep(0.5)
|
||||
if snippet.next and snippet.next.method == "wait":
|
||||
wait_for_string(snippet.next.data)
|
||||
elif snippet.next and snippet.next.method == "longwait":
|
||||
wait_for_string(snippet.next.data, 10*TIMEOUT)
|
||||
else:
|
||||
wait_for_prompt()
|
||||
# Verify return code
|
||||
check_exit_status()
|
||||
|
||||
|
||||
def action_copy(state, snippet):
|
||||
screen = capture_pane()
|
||||
matches = re.findall(snippet.data, screen, flags=re.DOTALL)
|
||||
if len(matches) == 0:
|
||||
raise Exception("Could not find regex {} in output.".format(snippet.data))
|
||||
# Arbitrarily get the most recent match
|
||||
match = matches[-1]
|
||||
# Remove line breaks (like a screen copy paste would do)
|
||||
match = match.replace('\n', '')
|
||||
logging.debug("Copied {} to clipboard.".format(match))
|
||||
state.clipboard = match
|
||||
|
||||
|
||||
def action_paste(state, snippet):
|
||||
logging.debug("Pasting {} from clipboard.".format(state.clipboard))
|
||||
action_keys(state, snippet, state.clipboard)
|
||||
|
||||
|
||||
def action_check(state, snippet):
|
||||
wait_for_prompt()
|
||||
check_exit_status()
|
||||
|
||||
|
||||
def action_open(state, snippet):
|
||||
# Cheap way to get node1's IP address
|
||||
screen = capture_pane()
|
||||
url = snippet.data.replace("/node1", "/{}".format(IPADDR))
|
||||
# This should probably be adapted to run on different OS
|
||||
if state.open_links:
|
||||
subprocess.check_output(["xdg-open", url])
|
||||
focus_browser()
|
||||
if state.interactive:
|
||||
print("Press any key to continue to next step...")
|
||||
click.getchar()
|
||||
|
||||
|
||||
def action_tmux(state, snippet):
|
||||
subprocess.check_call(["tmux"] + snippet.data.split())
|
||||
|
||||
|
||||
def action_unknown(state, snippet):
|
||||
logging.warning("Unknown method {}: {!r}".format(snippet.method, snippet.data))
|
||||
|
||||
|
||||
def run_snippet(state, snippet):
|
||||
logging.info("Running with method {}: {}".format(snippet.method, snippet.data))
|
||||
try:
|
||||
action = globals()["action_"+snippet.method]
|
||||
except KeyError:
|
||||
action = action_unknown
|
||||
try:
|
||||
action(state, snippet)
|
||||
result = "OK"
|
||||
except:
|
||||
result = "ERR"
|
||||
logging.exception("While running method {} with {!r}".format(snippet.method, snippet.data))
|
||||
# Try to recover
|
||||
try:
|
||||
wait_for_prompt()
|
||||
except:
|
||||
subprocess.check_call(["tmux", "new-window"])
|
||||
wait_for_prompt()
|
||||
outfile.write("{} SLIDE={} METHOD={} DATA={!r}\n".format(result, state.slide, snippet.method, snippet.data))
|
||||
outfile.flush()
|
||||
|
||||
|
||||
while True:
|
||||
state.save()
|
||||
slide = slides[state.slide]
|
||||
snippet = slide.snippets[state.snippet-1] if state.snippet else None
|
||||
if state.snippet and state.snippet <= len(slide.snippets):
|
||||
snippet = slide.snippets[state.snippet-1]
|
||||
else:
|
||||
snippet = None
|
||||
click.clear()
|
||||
print("[Slide {}/{}] [Snippet {}/{}] [simulate_type:{}] [verify_status:{}] "
|
||||
"[switch_desktop:{}] [sync_slides:{}] [open_links:{}] [run_hidden:{}]"
|
||||
@@ -385,7 +517,10 @@ while True:
|
||||
# continue until next timeout
|
||||
state.interactive = False
|
||||
elif command in ("y", "\r", " "):
|
||||
if not snippet:
|
||||
if snippet:
|
||||
run_snippet(state, snippet)
|
||||
move_forward()
|
||||
else:
|
||||
# Advance to next snippet
|
||||
# Advance until a slide that has snippets
|
||||
while not slides[state.slide].snippets:
|
||||
@@ -395,59 +530,5 @@ while True:
|
||||
break
|
||||
# And then advance to the snippet
|
||||
move_forward()
|
||||
continue
|
||||
method, data = snippet.method, snippet.data
|
||||
logging.info("Running with method {}: {}".format(method, data))
|
||||
if method == "keys":
|
||||
send_keys(data)
|
||||
elif method == "bash" or (method == "hide" and state.run_hidden):
|
||||
# Make sure that we're ready
|
||||
wait_for_prompt()
|
||||
# Strip leading spaces
|
||||
data = re.sub("\n +", "\n", data)
|
||||
# Remove backticks (they are used to highlight sections)
|
||||
data = data.replace('`', '')
|
||||
# Add "RETURN" at the end of the command :)
|
||||
data += "\n"
|
||||
# Send command
|
||||
send_keys(data)
|
||||
# Force a short sleep to avoid race condition
|
||||
time.sleep(0.5)
|
||||
if snippet.next and snippet.next.method == "wait":
|
||||
wait_for_string(snippet.next.data)
|
||||
elif snippet.next and snippet.next.method == "longwait":
|
||||
wait_for_string(snippet.next.data, 10*TIMEOUT)
|
||||
else:
|
||||
wait_for_prompt()
|
||||
# Verify return code
|
||||
check_exit_status()
|
||||
elif method == "copypaste":
|
||||
screen = capture_pane()
|
||||
matches = re.findall(data, screen, flags=re.DOTALL)
|
||||
if len(matches) == 0:
|
||||
raise Exception("Could not find regex {} in output.".format(data))
|
||||
# Arbitrarily get the most recent match
|
||||
match = matches[-1]
|
||||
# Remove line breaks (like a screen copy paste would do)
|
||||
match = match.replace('\n', '')
|
||||
send_keys(match + '\n')
|
||||
# FIXME: we should factor out the "bash" method
|
||||
wait_for_prompt()
|
||||
check_exit_status()
|
||||
elif method == "open":
|
||||
# Cheap way to get node1's IP address
|
||||
screen = capture_pane()
|
||||
url = data.replace("/node1", "/{}".format(IPADDR))
|
||||
# This should probably be adapted to run on different OS
|
||||
if state.open_links:
|
||||
subprocess.check_output(["xdg-open", url])
|
||||
focus_browser()
|
||||
if state.interactive:
|
||||
print("Press any key to continue to next step...")
|
||||
click.getchar()
|
||||
else:
|
||||
logging.warning("Unknown method {}: {!r}".format(method, data))
|
||||
move_forward()
|
||||
|
||||
else:
|
||||
logging.warning("Unknown command {}.".format(command))
|
||||
|
||||
@@ -14,6 +14,7 @@ once)
|
||||
./appendcheck.py $YAML.html
|
||||
done
|
||||
fi
|
||||
zip -qr slides.zip . && echo "Created slides.zip archive."
|
||||
;;
|
||||
|
||||
forever)
|
||||
|
||||
@@ -104,22 +104,6 @@ like Windows, macOS, Solaris, FreeBSD ...
|
||||
|
||||
---
|
||||
|
||||
## rkt
|
||||
|
||||
* Compares to `runc`.
|
||||
|
||||
* No daemon or API.
|
||||
|
||||
* Strong emphasis on security (through privilege separation).
|
||||
|
||||
* Networking has to be set up separately (e.g. through CNI plugins).
|
||||
|
||||
* Partial image management (pull, but no push).
|
||||
|
||||
(Image build is handled by separate tools.)
|
||||
|
||||
---
|
||||
|
||||
## CRI-O
|
||||
|
||||
* Designed to be used with Kubernetes as a simple, basic runtime.
|
||||
|
||||
137
slides/containers/Init_Systems.md
Normal file
137
slides/containers/Init_Systems.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Init systems and PID 1
|
||||
|
||||
In this chapter, we will consider:
|
||||
|
||||
- the role of PID 1 in the world of Docker,
|
||||
|
||||
- how to avoid some common pitfalls due to the misuse of init systems.
|
||||
|
||||
---
|
||||
|
||||
## What's an init system?
|
||||
|
||||
- On UNIX, the "init system" (or "init" in short) is PID 1.
|
||||
|
||||
- It is the first process started by the kernel when the system starts.
|
||||
|
||||
- It has multiple responsibilities:
|
||||
|
||||
- start every other process on the machine,
|
||||
|
||||
- reap orphaned zombie processes.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Orphaned zombie processes ?!?
|
||||
|
||||
- When a process exits (or "dies"), it becomes a "zombie".
|
||||
|
||||
(Zombie processes show up in `ps` or `top` with the status code `Z`.)
|
||||
|
||||
- Its parent process must *reap* the zombie process.
|
||||
|
||||
(This is done by calling `waitpid()` to retrieve the process' exit status.)
|
||||
|
||||
- When a process exits, if it has child processes, these processes are "orphaned."
|
||||
|
||||
- They are then re-parented to PID 1, init.
|
||||
|
||||
- Init therefore needs to take care of these orphaned processes when they exit.
|
||||
|
||||
---
|
||||
|
||||
## Don't use init systems in containers
|
||||
|
||||
- It's often tempting to use an init system or a process manager.
|
||||
|
||||
(Examples: *systemd*, *supervisord*...)
|
||||
|
||||
- Our containers are then called "system containers".
|
||||
|
||||
(By contrast with "application containers".)
|
||||
|
||||
- "System containers" are similar to lightweight virtual machines.
|
||||
|
||||
- They have multiple downsides:
|
||||
|
||||
- when starting multiple processes, their logs get mixed on stdout,
|
||||
|
||||
- if the application process dies, the container engine doesn't see it.
|
||||
|
||||
- Overall, they make it harder to operate troubleshoot containerized apps.
|
||||
|
||||
---
|
||||
|
||||
## Exceptions and workarounds
|
||||
|
||||
- Sometimes, it's convenient to run a real init system like *systemd*.
|
||||
|
||||
(Example: a CI system whose goal is precisely to test an init script or unit file.)
|
||||
|
||||
- If we need to run multiple processes: can we use multiple containers?
|
||||
|
||||
(Example: [this Compose file](https://github.com/jpetazzo/container.training/blob/master/compose/simple-k8s-control-plane/docker-compose.yaml) runs multiple processes together.)
|
||||
|
||||
- When deploying with Kubernetes:
|
||||
|
||||
- a container belong to a pod,
|
||||
|
||||
- a pod can have multiple containers.
|
||||
|
||||
---
|
||||
|
||||
## What about these zombie processes?
|
||||
|
||||
- Our application runs as PID 1 in the container.
|
||||
|
||||
- Our application may or may not be designed to reap zombie processes.
|
||||
|
||||
- If our application uses subprocesses and doesn't reap them ...
|
||||
|
||||
... this can lead to PID exhaustion!
|
||||
|
||||
(Or, more realistically, to a confusing herd of zombie processes.)
|
||||
|
||||
- How can we solve this?
|
||||
|
||||
---
|
||||
|
||||
## Tini to the rescue
|
||||
|
||||
- Docker can automatically provide a minimal `init` process.
|
||||
|
||||
- This is enabled with `docker run --init ...`
|
||||
|
||||
- It uses a small init system ([tini](https://github.com/krallin/tini)) as PID 1:
|
||||
|
||||
- it reaps zombies,
|
||||
|
||||
- it forwards signals,
|
||||
|
||||
- it exits when the child exits.
|
||||
|
||||
- It is totally transparent to our application.
|
||||
|
||||
- We should use it if our application creates subprocess but doesn't reap them.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## What about Kubernetes?
|
||||
|
||||
- Kubernetes does not expose that `--init` option.
|
||||
|
||||
- However, we can achieve the same result with [Process Namespace Sharing](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/).
|
||||
|
||||
- When Process Namespace Sharing is enabled, PID 1 will be `pause`.
|
||||
|
||||
- That `pause` process takes care of reaping zombies.
|
||||
|
||||
- Process Namespace Sharing is available since Kubernetes 1.16.
|
||||
|
||||
- If you're using an older version of Kubernetes ...
|
||||
|
||||
... you might have to add `tini` explicitly to your Docker image.
|
||||
@@ -102,29 +102,44 @@ class: extra-details
|
||||
|
||||
---
|
||||
|
||||
## Docker Desktop for Mac and Docker Desktop for Windows
|
||||
## Docker Desktop
|
||||
|
||||
* Special Docker Editions that integrate well with their respective host OS
|
||||
* Special Docker edition available for Mac and Windows
|
||||
|
||||
* Provide user-friendly GUI to edit Docker configuration and settings
|
||||
* Integrates well with the host OS:
|
||||
|
||||
* Leverage the host OS virtualization subsystem (e.g. the [Hypervisor API](https://developer.apple.com/documentation/hypervisor) on macOS)
|
||||
* installed like normal user applications on the host
|
||||
|
||||
* Installed like normal user applications on the host
|
||||
* provides user-friendly GUI to edit Docker configuration and settings
|
||||
|
||||
* Under the hood, they both run a tiny VM (transparent to our daily use)
|
||||
* Only support running one Docker VM at a time ...
|
||||
|
||||
* Access network resources like normal applications
|
||||
<br/>(and therefore, play better with enterprise VPNs and firewalls)
|
||||
|
||||
* Support filesystem sharing through volumes (we'll talk about this later)
|
||||
|
||||
* They only support running one Docker VM at a time ...
|
||||
<br/>
|
||||
... but we can use `docker-machine`, the Docker Toolbox, VirtualBox, etc. to get a cluster.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Docker Desktop internals
|
||||
|
||||
* Leverages the host OS virtualization subsystem
|
||||
|
||||
(e.g. the [Hypervisor API](https://developer.apple.com/documentation/hypervisor) on macOS)
|
||||
|
||||
* Under the hood, runs a tiny VM
|
||||
|
||||
(transparent to our daily use)
|
||||
|
||||
* Accesses network resources like normal applications
|
||||
|
||||
(and therefore, plays better with enterprise VPNs and firewalls)
|
||||
|
||||
* Supports filesystem sharing through volumes
|
||||
|
||||
(we'll talk about this later)
|
||||
|
||||
---
|
||||
|
||||
## Running Docker on macOS and Windows
|
||||
|
||||
When you execute `docker version` from the terminal:
|
||||
|
||||
47
slides/containers/Pods_Anatomy.md
Normal file
47
slides/containers/Pods_Anatomy.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Container Super-structure
|
||||
|
||||
- Multiple orchestration platforms support some kind of container super-structure.
|
||||
|
||||
(i.e., a construct or abstraction bigger than a single container.)
|
||||
|
||||
- For instance, on Kubernetes, this super-structure is called a *pod*.
|
||||
|
||||
- A pod is a group of containers (it could be a single container, too).
|
||||
|
||||
- These containers run together, on the same host.
|
||||
|
||||
(A pod cannot straddle multiple hosts.)
|
||||
|
||||
- All the containers in a pod have the same IP address.
|
||||
|
||||
- How does that map to the Docker world?
|
||||
|
||||
---
|
||||
|
||||
class: pic
|
||||
|
||||
## Anatomy of a Pod
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Pods in Docker
|
||||
|
||||
- The containers inside a pod share the same network namespace.
|
||||
|
||||
(Just like when using `docker run --net=container:<container_id>` with the CLI.)
|
||||
|
||||
- As a result, they can communicate together over `localhost`.
|
||||
|
||||
- In addition to "our" containers, the pod has a special container, the *sandbox*.
|
||||
|
||||
- That container uses a special image: `k8s.gcr.io/pause`.
|
||||
|
||||
(This is visible when listing containers running on a Kubernetes node.)
|
||||
|
||||
- Containers within a pod have independent filesystems.
|
||||
|
||||
- They can share directories by using a mechanism called *volumes.*
|
||||
|
||||
(Which is similar to the concept of volumes in Docker.)
|
||||
@@ -100,3 +100,25 @@ class: extra-details
|
||||
* In "Build rules" block near page bottom, put `/www` in "Build Context" column (or whichever directory the Dockerfile is in).
|
||||
* Click "Save and Build" to build the repository immediately (without waiting for a git push).
|
||||
* Subsequent builds will happen automatically, thanks to GitHub hooks.
|
||||
|
||||
---
|
||||
|
||||
## Building on the fly
|
||||
|
||||
- Some services can build images on the fly from a repository
|
||||
|
||||
- Example: [ctr.run](https://ctr.run/)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Use ctr.run to automatically build a container image and run it:
|
||||
```bash
|
||||
docker run ctr.run/github.com/undefinedlabs/hello-world
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
There might be a long pause before the first layer is pulled,
|
||||
because the API behind `docker pull` doesn't allow to stream build logs, and there is no feedback during the build.
|
||||
|
||||
It is possible to view the build logs by setting up an account on [ctr.run](https://ctr.run/).
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../swarm/links.md
|
||||
12
slides/containers/links.md
Normal file
12
slides/containers/links.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Links and resources
|
||||
|
||||
- [Docker Community Slack](https://community.docker.com/registrations/groups/4316)
|
||||
- [Docker Community Forums](https://forums.docker.com/)
|
||||
- [Docker Hub](https://hub.docker.com)
|
||||
- [Docker Blog](https://blog.docker.com/)
|
||||
- [Docker documentation](https://docs.docker.com/)
|
||||
- [Docker on StackOverflow](https://stackoverflow.com/questions/tagged/docker)
|
||||
- [Docker on Twitter](https://twitter.com/docker)
|
||||
- [Play With Docker Hands-On Labs](https://training.play-with-docker.com/)
|
||||
|
||||
.footnote[These slides (and future updates) are on → https://container.training/]
|
||||
1
slides/images/kubernetes_pods.drawio
Normal file
1
slides/images/kubernetes_pods.drawio
Normal file
@@ -0,0 +1 @@
|
||||
<mxfile host="www.draw.io" modified="2019-12-06T15:04:22.728Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/71.0" etag="zsQLtxL9GRXJF3jcROIq" version="12.3.7" type="device" pages="1"><diagram id="hOpsmMj0j3CSse8MyRSQ" name="Page-1">3VhLU9swEP41nmkPzcR2EpIjCaHtUEo6HCi9dBRb2BoUy8hyHvz6rmzJD9mBQBJgmoMjrVcr6dtvVytb7mSx/spRHF4yH1PL6fpryz2zHMcZ9Rz4k5JNLrFtd5RLAk58JSsF1+QRK2FXSVPi46SmKBijgsR1oceiCHuiJkOcs1Vd7Y7R+qwxCnBDcO0h2pTeEF+EuXTonJTyb5gEoZ7ZHqj9LZBWVjtJQuSzVUXkTi13whkTeWuxnmAq0dO45OPOt7wtFsZxJHYZ8PfXAwsvwsvLP5duOpn2bx4ufnyx1WqXiKZqx5YzoGBw7JMlNAPZDFkiQOkTGF8iDk9K5vC8T+eYYnhz3ul0Putxc66HaQkoVIwpNMRGQ8xZGvlYrrILr1chEfg6Rp58uwJWyfnFgkLPhiaiJIigzXPMlbUl5gKvtwJjF3ADUTFbYME3oKIGDJWDFEXdruqvSn/3ekoWVn2tPYsUx4LCdOkGaChPvMQrDafMmL8fbiHj5JFFAmmBhIwAz08VoILFR4GztyOaheLB0XQaaMYoTXCeNAQiEeb7YXsA0AoubeogVlBz3RbUjgaa2wAtCki0/nBA2S38elukei0Z1AAJR/6pPIug51GUJMSr4wJ755vf1c6tBLTT192ztQI47210b01EMQzalVHQKwfJTjHGh/NNLQ3TOVtNS4FykFR52j2wO5ZyDz9PIIF4gMVz0dl0d8Wd/RZvahnHFAmyrC+3zcVqhhkjkaiwqWvk/oHBknybalT1cDUN9Q1DtmEox6FhCGiBNhW1WCok2xfcM7Kr7dYOfWjkFks6F5i+nuHNGiHm0miI00TSZR0ziOiPl0SdlpP8bXOD3TzJd0sOCfBFaHHEIvxBE0a2znMiUcmUd00g7xXwPSNOHbOG2zXgTUNFJjl2wA/eIODtYQttG7eCn1isGL+3JIQDtJDxnD9B8n02yeU7XgkaxiO0wEmWLLKbEydRsON1AvKHaL8zeMBBSFPN2ndBfD+jM8cJeUTzzJSks/IO2O2Prf6ZnM4dUwTXnjHy7oMswU0YZTyb2r3LftIOE8BSJm2PyrBoSW7q2qqmtAo6VgPmicyyNRV2O1Bl92rM0XXwvkfm0AigugF2d5dgYVD0MKRslqQN3wNTYpxlTIGfP3LmhQ+vUkGJTLKZ3Ef8/gpGEZHlwE5XJsgk/zThHOmscp3mWTVoyYPDox1VB6hjP3r2t/XnKBP0F5d7hiF7aITBlux/sFgY/E+x4JhV+LvHwsn+saBLLV1P3VZrK7lxe1QWXtX6bIY5gW3Ig+pFJdUOd7KcNu8VfeaHoZNXBp9jlvlm+f7q4INu+T02Vy8/a7vTfw==</diagram></mxfile>
|
||||
3
slides/images/kubernetes_pods.svg
Normal file
3
slides/images/kubernetes_pods.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -1,5 +1,14 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# coding: utf-8
|
||||
|
||||
FLAGS=dict(
|
||||
cz=u"🇨🇿",
|
||||
de=u"🇩🇪",
|
||||
fr=u"🇫🇷",
|
||||
uk=u"🇬🇧",
|
||||
us=u"🇺🇸",
|
||||
)
|
||||
|
||||
TEMPLATE="""<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
@@ -34,7 +43,7 @@ TEMPLATE="""<html>
|
||||
|
||||
{% for item in coming_soon %}
|
||||
<tr>
|
||||
<td>{{ item.title }}</td>
|
||||
<td>{{ item.flag }} {{ item.title }}</td>
|
||||
<td>{% if item.slides %}<a class="slides" href="{{ item.slides }}" />{% endif %}</td>
|
||||
<td>{% if item.attend %}<a class="attend" href="{{ item.attend }}" />
|
||||
{% else %}
|
||||
@@ -123,13 +132,13 @@ TEMPLATE="""<html>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>""".decode("utf-8")
|
||||
</html>"""
|
||||
|
||||
import datetime
|
||||
import jinja2
|
||||
import yaml
|
||||
|
||||
items = yaml.load(open("index.yaml"))
|
||||
items = yaml.safe_load(open("index.yaml"))
|
||||
|
||||
# Items with a date correspond to scheduled sessions.
|
||||
# Items without a date correspond to self-paced content.
|
||||
@@ -160,6 +169,7 @@ for item in items:
|
||||
item["prettydate"] = date_begin.strftime("%B %d{}, %Y").format(suffix)
|
||||
item["begin"] = date_begin
|
||||
item["end"] = date_end
|
||||
item["flag"] = FLAGS.get(item.get("country"),"")
|
||||
|
||||
today = datetime.date.today()
|
||||
coming_soon = [i for i in items if i.get("date") and i["end"] >= today]
|
||||
@@ -177,10 +187,10 @@ with open("index.html", "w") as f:
|
||||
past_workshops=past_workshops,
|
||||
self_paced=self_paced,
|
||||
recorded_workshops=recorded_workshops
|
||||
).encode("utf-8"))
|
||||
))
|
||||
|
||||
with open("past.html", "w") as f:
|
||||
f.write(template.render(
|
||||
title="Container Training",
|
||||
all_past_workshops=past_workshops
|
||||
).encode("utf-8"))
|
||||
))
|
||||
|
||||
@@ -1,3 +1,66 @@
|
||||
- date: 2020-03-06
|
||||
country: uk
|
||||
city: London
|
||||
event: QCON
|
||||
speaker: jpetazzo
|
||||
title: Kubernetes Intensive Course
|
||||
attend: https://qconlondon.com/london2020/workshop/kubernetes-intro
|
||||
#slides: https://qconuk2019.container.training/
|
||||
|
||||
- date: 2020-03-05
|
||||
country: uk
|
||||
city: London
|
||||
event: QCON
|
||||
speaker: jpetazzo
|
||||
title: Docker Intensive Course
|
||||
attend: https://qconlondon.com/london2020/workshop/docker-intensive-course
|
||||
#slides: https://qconuk2019.container.training/
|
||||
|
||||
- date: 2020-02-03
|
||||
country: fr
|
||||
city: Paris
|
||||
event: ENIX SAS
|
||||
speaker: jpetazzo
|
||||
title: Fondamentaux Conteneurs et Docker (in French)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/
|
||||
|
||||
- date: 2020-02-04
|
||||
country: fr
|
||||
city: Paris
|
||||
event: ENIX SAS
|
||||
speaker: jpetazzo
|
||||
title: Fondamentaux Orchestration et Kubernetes (in French)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/
|
||||
|
||||
- date: 2020-02-05
|
||||
country: fr
|
||||
city: Paris
|
||||
event: ENIX SAS
|
||||
speaker: jpetazzo
|
||||
title: Kubernetes et Méthodologies DevOps (in French)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/
|
||||
|
||||
- date: 2020-02-06
|
||||
country: fr
|
||||
city: Paris
|
||||
event: ENIX SAS
|
||||
speaker: jpetazzo
|
||||
title: Kubernetes Avancé (in French)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/
|
||||
|
||||
- date: 2020-02-07
|
||||
country: fr
|
||||
city: Paris
|
||||
event: ENIX SAS
|
||||
speaker: jpetazzo
|
||||
title: Opérer Kubernetes (in French)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/
|
||||
|
||||
- date: [2019-11-04, 2019-11-05]
|
||||
country: de
|
||||
city: Berlin
|
||||
@@ -5,6 +68,7 @@
|
||||
speaker: jpetazzo
|
||||
title: Deploying and scaling applications with Kubernetes
|
||||
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/79109
|
||||
slides: https://velocity-2019-11.container.training/
|
||||
|
||||
- date: 2019-11-13
|
||||
country: fr
|
||||
|
||||
@@ -118,9 +118,9 @@ installed and set up `kubectl` to communicate with your cluster.
|
||||
<!--
|
||||
```wait Connected to localhost```
|
||||
```keys INFO server```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
```keys QUIT```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
- Terminate the port forwarder:
|
||||
|
||||
@@ -547,7 +547,7 @@ It's important to note a couple of details in these flags...
|
||||
|
||||
- Exit the container with `exit` or `^D`
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
<!-- ```key ^D``` -->
|
||||
|
||||
]
|
||||
|
||||
@@ -667,17 +667,12 @@ 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:
|
||||
- There are a few tools to help us with that
|
||||
|
||||
https://github.com/aquasecurity/kubectl-who-can
|
||||
- [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
|
||||
|
||||
- This is one way to install it:
|
||||
```bash
|
||||
docker run --rm -v /usr/local/bin:/go/bin golang \
|
||||
go get -v github.com/aquasecurity/kubectl-who-can
|
||||
```
|
||||
- [Review Access (aka Rakkess)](https://github.com/corneliusweig/rakkess)
|
||||
|
||||
- This is one way to use it:
|
||||
```bash
|
||||
kubectl-who-can create pods
|
||||
```
|
||||
- Both are available as standalone programs, or as plugins for `kubectl`
|
||||
|
||||
(`kubectl` plugins can be installed and managed with `krew`)
|
||||
|
||||
@@ -109,7 +109,7 @@ spec:
|
||||
|
||||
<!--
|
||||
```longwait latest: digest: sha256:```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -174,7 +174,7 @@ spec:
|
||||
|
||||
<!--
|
||||
```longwait registry:5000/rng-kaniko:latest:```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -15,26 +15,3 @@
|
||||
- `dockercoins/webui:v0.1`
|
||||
|
||||
- `dockercoins/worker:v0.1`
|
||||
|
||||
---
|
||||
|
||||
## Setting `$REGISTRY` and `$TAG`
|
||||
|
||||
- In the upcoming exercises and labs, we use a couple of environment variables:
|
||||
|
||||
- `$REGISTRY` as a prefix to all image names
|
||||
|
||||
- `$TAG` as the image version tag
|
||||
|
||||
- For example, the worker image is `$REGISTRY/worker:$TAG`
|
||||
|
||||
- If you copy-paste the commands in these exercises:
|
||||
|
||||
**make sure that you set `$REGISTRY` and `$TAG` first!**
|
||||
|
||||
- For example:
|
||||
```bash
|
||||
export REGISTRY=dockercoins TAG=v0.1
|
||||
```
|
||||
|
||||
(this will expand `$REGISTRY/worker:$TAG` to `dockercoins/worker:v0.1`)
|
||||
|
||||
@@ -44,21 +44,37 @@
|
||||
|
||||
## Other things that Kubernetes can do for us
|
||||
|
||||
- Basic autoscaling
|
||||
- Autoscaling
|
||||
|
||||
- Blue/green deployment, canary deployment
|
||||
(straightforward on CPU; more complex on other metrics)
|
||||
|
||||
- Long running services, but also batch (one-off) jobs
|
||||
- Ressource management and scheduling
|
||||
|
||||
- Overcommit our cluster and *evict* low-priority jobs
|
||||
(reserve CPU/RAM for containers; placement constraints)
|
||||
|
||||
- Run services with *stateful* data (databases etc.)
|
||||
- Advanced rollout patterns
|
||||
|
||||
- Fine-grained access control defining *what* can be done by *whom* on *which* resources
|
||||
(blue/green deployment, canary deployment)
|
||||
|
||||
- Integrating third party services (*service catalog*)
|
||||
---
|
||||
|
||||
- Automating complex tasks (*operators*)
|
||||
## More things that Kubernetes can do for us
|
||||
|
||||
- Batch jobs
|
||||
|
||||
(one-off; parallel; also cron-style periodic execution)
|
||||
|
||||
- Fine-grained access control
|
||||
|
||||
(defining *what* can be done by *whom* on *which* resources)
|
||||
|
||||
- Stateful services
|
||||
|
||||
(databases, message queues, etc.)
|
||||
|
||||
- Automating complex tasks with *operators*
|
||||
|
||||
(e.g. database replication, failover, etc.)
|
||||
|
||||
---
|
||||
|
||||
@@ -183,6 +199,30 @@ class: extra-details
|
||||
|
||||
class: extra-details
|
||||
|
||||
## How many nodes should a cluster have?
|
||||
|
||||
- There is no particular constraint
|
||||
|
||||
(no need to have an odd number of nodes for quorum)
|
||||
|
||||
- A cluster can have zero node
|
||||
|
||||
(but then it won't be able to start any pods)
|
||||
|
||||
- For testing and development, having a single node is fine
|
||||
|
||||
- For production, make sure that you have extra capacity
|
||||
|
||||
(so that your workload still fits if you lose a node or a group of nodes)
|
||||
|
||||
- Kubernetes is tested with [up to 5000 nodes](https://kubernetes.io/docs/setup/best-practices/cluster-large/)
|
||||
|
||||
(however, running a cluster of that size requires a lot of tuning)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Do we need to run Docker at all?
|
||||
|
||||
No!
|
||||
@@ -191,11 +231,29 @@ No!
|
||||
|
||||
- By default, Kubernetes uses the Docker Engine to run containers
|
||||
|
||||
- We could also use `rkt` ("Rocket") from CoreOS
|
||||
- We can leverage other pluggable runtimes through the *Container Runtime Interface*
|
||||
|
||||
- Or leverage other pluggable runtimes through the *Container Runtime Interface*
|
||||
- <del>We could also use `rkt` ("Rocket") from CoreOS</del> (deprecated)
|
||||
|
||||
(like CRI-O, or containerd)
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Some runtimes available through CRI
|
||||
|
||||
- [containerd](https://github.com/containerd/containerd/blob/master/README.md)
|
||||
|
||||
- maintained by Docker, IBM, and community
|
||||
- used by Docker Engine, microk8s, k3s, GKE; also standalone
|
||||
- comes with its own CLI, `ctr`
|
||||
|
||||
- [CRI-O](https://github.com/cri-o/cri-o/blob/master/README.md):
|
||||
|
||||
- maintained by Red Hat, SUSE, and community
|
||||
- used by OpenShift and Kubic
|
||||
- designed specifically as a minimal runtime for Kubernetes
|
||||
|
||||
- [And more](https://kubernetes.io/docs/setup/production-environment/container-runtimes/)
|
||||
|
||||
---
|
||||
|
||||
@@ -265,6 +323,48 @@ class: pic
|
||||
|
||||
---
|
||||
|
||||
## Scaling
|
||||
|
||||
- How would we scale the pod shown on the previous slide?
|
||||
|
||||
- **Do** create additional pods
|
||||
|
||||
- each pod can be on a different node
|
||||
|
||||
- each pod will have its own IP address
|
||||
|
||||
- **Do not** add more NGINX containers in the pod
|
||||
|
||||
- all the NGINX containers would be on the same node
|
||||
|
||||
- they would all have the same IP address
|
||||
<br/>(resulting in `Address alreading in use` errors)
|
||||
|
||||
---
|
||||
|
||||
## Together or separate
|
||||
|
||||
- Should we put e.g. a web application server and a cache together?
|
||||
<br/>
|
||||
("cache" being something like e.g. Memcached or Redis)
|
||||
|
||||
- Putting them **in the same pod** means:
|
||||
|
||||
- they have to be scaled together
|
||||
|
||||
- they can communicate very efficiently over `localhost`
|
||||
|
||||
- Putting them **in different pods** means:
|
||||
|
||||
- they can be scaled separately
|
||||
|
||||
- they must communicate over remote IP addresses
|
||||
<br/>(incurring more latency, lower performance)
|
||||
|
||||
- Both scenarios can make sense, depending on our goals
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -193,7 +193,12 @@
|
||||
|
||||
- Best practice: set a memory limit, and pass it to the runtime
|
||||
|
||||
(see [this blog post](https://very-serio.us/2017/12/05/running-jvms-in-kubernetes/) for a detailed example)
|
||||
- Note: recent versions of the JVM can do this automatically
|
||||
|
||||
(see [JDK-8146115](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115))
|
||||
and
|
||||
[this blog post](https://very-serio.us/2017/12/05/running-jvms-in-kubernetes/)
|
||||
for detailed examples)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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!
|
||||
@@ -110,20 +110,22 @@
|
||||
```bash vim rng.yml```
|
||||
```wait kind: Deployment```
|
||||
```keys /Deployment```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
```keys cwDaemonSet```
|
||||
```keys ^[``` ]
|
||||
```key ^[``` ]
|
||||
```keys :wq```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
- Save, quit
|
||||
|
||||
- Try to create our new resource:
|
||||
```
|
||||
```bash
|
||||
kubectl apply -f rng.yml
|
||||
```
|
||||
|
||||
<!-- ```wait error:``` -->
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
@@ -501,11 +503,11 @@ be any interruption.*
|
||||
<!--
|
||||
```wait Please edit the object below```
|
||||
```keys /app: rng```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
```keys noenabled: yes```
|
||||
```keys ^[``` ]
|
||||
```key ^[``` ]
|
||||
```keys :wq```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -538,19 +540,18 @@ be any interruption.*
|
||||
|
||||
.exercise[
|
||||
|
||||
- Update the service to add `enabled: "yes"` to its selector:
|
||||
```bash
|
||||
kubectl edit service rng
|
||||
```
|
||||
- Update the YAML manifest of the service
|
||||
|
||||
- Add `enabled: "yes"` to its selector
|
||||
|
||||
<!--
|
||||
```wait Please edit the object below```
|
||||
```keys /app: rng```
|
||||
```keys ^J```
|
||||
```keys noenabled: "yes"```
|
||||
```keys ^[``` ]
|
||||
```keys /yes```
|
||||
```key ^J```
|
||||
```keys cw"yes"```
|
||||
```key ^[``` ]
|
||||
```keys :wq```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -589,16 +590,25 @@ If we did everything correctly, the web UI shouldn't show any change.
|
||||
```bash
|
||||
POD=$(kubectl get pod -l app=rng,pod-template-hash -o name)
|
||||
kubectl logs --tail 1 --follow $POD
|
||||
|
||||
```
|
||||
(We should see a steady stream of HTTP logs)
|
||||
|
||||
<!--
|
||||
```wait HTTP/1.1```
|
||||
```tmux split-pane -v```
|
||||
-->
|
||||
|
||||
- In another window, remove the label from the pod:
|
||||
```bash
|
||||
kubectl label pod -l app=rng,pod-template-hash enabled-
|
||||
```
|
||||
(The stream of HTTP logs should stop immediately)
|
||||
|
||||
<!--
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
There might be a slight change in the web UI (since we removed a bit
|
||||
|
||||
@@ -481,13 +481,13 @@ docker run alpine echo hello world
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the file `kubeconfig.kubelet` with `kubectl`:
|
||||
- Create the file `~/.kube/config` with `kubectl`:
|
||||
```bash
|
||||
kubectl --kubeconfig kubeconfig.kubelet config \
|
||||
kubectl config \
|
||||
set-cluster localhost --server http://localhost:8080
|
||||
kubectl --kubeconfig kubeconfig.kubelet config \
|
||||
kubectl config \
|
||||
set-context localhost --cluster localhost
|
||||
kubectl --kubeconfig kubeconfig.kubelet config \
|
||||
kubectl config \
|
||||
use-context localhost
|
||||
```
|
||||
|
||||
@@ -495,19 +495,7 @@ docker run alpine echo hello world
|
||||
|
||||
---
|
||||
|
||||
## All Kubernetes clients can use `kubeconfig`
|
||||
|
||||
- The `kubeconfig.kubelet` file has the same format as e.g. `~/.kubeconfig`
|
||||
|
||||
- All Kubernetes clients can use a similar file
|
||||
|
||||
- The `kubectl config` commands can be used to manipulate these files
|
||||
|
||||
- This highlights that kubelet is a "normal" client of the API server
|
||||
|
||||
---
|
||||
|
||||
## Our `kubeconfig.kubelet` file
|
||||
## Our `~/.kube/config` file
|
||||
|
||||
The file that we generated looks like the one below.
|
||||
|
||||
@@ -533,9 +521,9 @@ clusters:
|
||||
|
||||
.exercise[
|
||||
|
||||
- Start kubelet with that `kubeconfig.kubelet` file:
|
||||
- Start kubelet with that kubeconfig file:
|
||||
```bash
|
||||
kubelet --kubeconfig kubeconfig.kubelet
|
||||
kubelet --kubeconfig ~/.kube/config
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
@@ -162,6 +162,8 @@ Instead, it has the fields expected in a DaemonSet.
|
||||
kubectl diff -f web.yaml
|
||||
```
|
||||
|
||||
<!-- ```wait status:``` -->
|
||||
|
||||
]
|
||||
|
||||
Note: we don't need to specify `--validate=false` here.
|
||||
|
||||
@@ -1,41 +1,3 @@
|
||||
## Questions to ask before adding healthchecks
|
||||
|
||||
- Do we want liveness, readiness, both?
|
||||
|
||||
(sometimes, we can use the same check, but with different failure thresholds)
|
||||
|
||||
- Do we have existing HTTP endpoints that we can use?
|
||||
|
||||
- Do we need to add new endpoints, or perhaps use something else?
|
||||
|
||||
- Are our healthchecks likely to use resources and/or slow down the app?
|
||||
|
||||
- Do they depend on additional services?
|
||||
|
||||
(this can be particularly tricky, see next slide)
|
||||
|
||||
---
|
||||
|
||||
## Healthchecks and dependencies
|
||||
|
||||
- A good healthcheck should always indicate the health of the service itself
|
||||
|
||||
- It should not be affected by the state of the service's dependencies
|
||||
|
||||
- Example: a web server requiring a database connection to operate
|
||||
|
||||
(make sure that the healthcheck can report "OK" even if the database is down;
|
||||
<br/>
|
||||
because it won't help us to restart the web server if the issue is with the DB!)
|
||||
|
||||
- Example: a microservice calling other microservices
|
||||
|
||||
- Example: a worker process
|
||||
|
||||
(these will generally require minor code changes to report health)
|
||||
|
||||
---
|
||||
|
||||
## Adding healthchecks to an app
|
||||
|
||||
- Let's add healthchecks to DockerCoins!
|
||||
@@ -370,24 +332,4 @@ class: extra-details
|
||||
|
||||
(and have gcr.io/pause take care of the reaping)
|
||||
|
||||
---
|
||||
|
||||
## Healthchecks for worker
|
||||
|
||||
- Readiness isn't useful
|
||||
|
||||
(because worker isn't a backend for a service)
|
||||
|
||||
- Liveness may help us restart a broken worker, but how can we check it?
|
||||
|
||||
- Embedding an HTTP server is an option
|
||||
|
||||
(but it has a high potential for unwanted side effects and false positives)
|
||||
|
||||
- Using a "lease" file can be relatively easy:
|
||||
|
||||
- touch a file during each iteration of the main loop
|
||||
|
||||
- check the timestamp of that file from an exec probe
|
||||
|
||||
- Writing logs (and checking them from the probe) also works
|
||||
- Discussion of this in [Video - 10 Ways to Shoot Yourself in the Foot with Kubernetes, #9 Will Surprise You](https://www.youtube.com/watch?v=QKI-JRs2RIE)
|
||||
|
||||
@@ -42,9 +42,11 @@
|
||||
|
||||
- internal corruption (causing all requests to error)
|
||||
|
||||
- If the liveness probe fails *N* consecutive times, the container is killed
|
||||
- Anything where our incident response would be "just restart/reboot it"
|
||||
|
||||
- *N* is the `failureThreshold` (3 by default)
|
||||
.warning[**Do not** use liveness probes for problems that can't be fixed by a restart]
|
||||
|
||||
- Otherwise we just restart our pods for no reason, creating useless load
|
||||
|
||||
---
|
||||
|
||||
@@ -52,7 +54,7 @@
|
||||
|
||||
- Indicates if the container is ready to serve traffic
|
||||
|
||||
- If a container becomes "unready" (let's say busy!) it might be ready again soon
|
||||
- If a container becomes "unready" it might be ready again soon
|
||||
|
||||
- If the readiness probe fails:
|
||||
|
||||
@@ -66,19 +68,79 @@
|
||||
|
||||
## When to use a readiness probe
|
||||
|
||||
- To indicate temporary failures
|
||||
- To indicate failure due to an external cause
|
||||
|
||||
- the application can only service *N* parallel connections
|
||||
- database is down or unreachable
|
||||
|
||||
- the runtime is busy doing garbage collection or initial data load
|
||||
- mandatory auth or other backend service unavailable
|
||||
|
||||
- The container is marked as "not ready" after `failureThreshold` failed attempts
|
||||
- To indicate temporary failure or unavailability
|
||||
|
||||
(3 by default)
|
||||
- application can only service *N* parallel connections
|
||||
|
||||
- It is marked again as "ready" after `successThreshold` successful attempts
|
||||
- runtime is busy doing garbage collection or initial data load
|
||||
|
||||
(1 by default)
|
||||
- For processes that take a long time to start
|
||||
|
||||
(more on that later)
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- If a web server depends on a database to function, and the database is down:
|
||||
|
||||
- the web server's liveness probe should succeed
|
||||
|
||||
- the web server's readiness probe should fail
|
||||
|
||||
- Same thing for any hard dependency (without which the container can't work)
|
||||
|
||||
.warning[**Do not** fail liveness probes for problems that are external to the container]
|
||||
|
||||
---
|
||||
|
||||
## Timing and thresholds
|
||||
|
||||
- Probes are executed at intervals of `periodSeconds` (default: 10)
|
||||
|
||||
- The timeout for a probe is set with `timeoutSeconds` (default: 1)
|
||||
|
||||
.warning[If a probe takes longer than that, it is considered as a FAIL]
|
||||
|
||||
- A probe is considered successful after `successThreshold` successes (default: 1)
|
||||
|
||||
- A probe is considered failing after `failureThreshold` failures (default: 3)
|
||||
|
||||
- A probe can have an `initialDelaySeconds` parameter (default: 0)
|
||||
|
||||
- Kubernetes will wait that amount of time before running the probe for the first time
|
||||
|
||||
(this is important to avoid killing services that take a long time to start)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Startup probe
|
||||
|
||||
- Kubernetes 1.16 introduces a third type of probe: `startupProbe`
|
||||
|
||||
(it is in `alpha` in Kubernetes 1.16)
|
||||
|
||||
- It can be used to indicate "container not ready *yet*"
|
||||
|
||||
- process is still starting
|
||||
|
||||
- loading external data, priming caches
|
||||
|
||||
- Before Kubernetes 1.16, we had to use the `initialDelaySeconds` parameter
|
||||
|
||||
(available for both liveness and readiness probes)
|
||||
|
||||
- `initialDelaySeconds` is a rigid delay (always wait X before running probes)
|
||||
|
||||
- `startupProbe` works better when a container start time can vary a lot
|
||||
|
||||
---
|
||||
|
||||
@@ -112,10 +174,12 @@
|
||||
|
||||
(instead of serving errors or timeouts)
|
||||
|
||||
- Overloaded backends get removed from load balancer rotation
|
||||
- Unavailable backends get removed from load balancer rotation
|
||||
|
||||
(thus improving response times across the board)
|
||||
|
||||
- If a probe is not defined, it's as if there was an "always successful" probe
|
||||
|
||||
---
|
||||
|
||||
## Example: HTTP probe
|
||||
@@ -165,14 +229,56 @@ If the Redis process becomes unresponsive, it will be killed.
|
||||
|
||||
---
|
||||
|
||||
## Details about liveness and readiness probes
|
||||
## Questions to ask before adding healthchecks
|
||||
|
||||
- Probes are executed at intervals of `periodSeconds` (default: 10)
|
||||
- Do we want liveness, readiness, both?
|
||||
|
||||
- The timeout for a probe is set with `timeoutSeconds` (default: 1)
|
||||
(sometimes, we can use the same check, but with different failure thresholds)
|
||||
|
||||
- A probe is considered successful after `successThreshold` successes (default: 1)
|
||||
- Do we have existing HTTP endpoints that we can use?
|
||||
|
||||
- A probe is considered failing after `failureThreshold` failures (default: 3)
|
||||
- Do we need to add new endpoints, or perhaps use something else?
|
||||
|
||||
- If a probe is not defined, it's as if there was an "always successful" probe
|
||||
- Are our healthchecks likely to use resources and/or slow down the app?
|
||||
|
||||
- Do they depend on additional services?
|
||||
|
||||
(this can be particularly tricky, see next slide)
|
||||
|
||||
---
|
||||
|
||||
## Healthchecks and dependencies
|
||||
|
||||
- Liveness checks should not be influenced by the state of external services
|
||||
|
||||
- All checks should reply quickly (by default, less than 1 second)
|
||||
|
||||
- Otherwise, they are considered to fail
|
||||
|
||||
- This might require to check the health of dependencies asynchronously
|
||||
|
||||
(e.g. if a database or API might be healthy but still take more than
|
||||
1 second to reply, we should check the status asynchronously and report
|
||||
a cached status)
|
||||
|
||||
---
|
||||
|
||||
## Healthchecks for workers
|
||||
|
||||
(In that context, worker = process that doesn't accept connections)
|
||||
|
||||
- Readiness isn't useful
|
||||
|
||||
(because workers aren't backends for a service)
|
||||
|
||||
- Liveness may help us restart a broken worker, but how can we check it?
|
||||
|
||||
- Embedding an HTTP server is a (potentially expensive) option
|
||||
|
||||
- Using a "lease" file can be relatively easy:
|
||||
|
||||
- touch a file during each iteration of the main loop
|
||||
|
||||
- check the timestamp of that file from an exec probe
|
||||
|
||||
- Writing logs (and checking them from the probe) also works
|
||||
|
||||
239
slides/k8s/helm-chart-format.md
Normal file
239
slides/k8s/helm-chart-format.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Helm chart format
|
||||
|
||||
- What exactly is a chart?
|
||||
|
||||
- What's in it?
|
||||
|
||||
- What would be involved in creating a chart?
|
||||
|
||||
(we won't create a chart, but we'll see the required steps)
|
||||
|
||||
---
|
||||
|
||||
## What is a chart
|
||||
|
||||
- A chart is a set of files
|
||||
|
||||
- Some of these files are mandatory for the chart to be viable
|
||||
|
||||
(more on that later)
|
||||
|
||||
- These files are typically packed in a tarball
|
||||
|
||||
- These tarballs are stored in "repos"
|
||||
|
||||
(which can be static HTTP servers)
|
||||
|
||||
- We can install from a repo, from a local tarball, or an unpacked tarball
|
||||
|
||||
(the latter option is preferred when developing a chart)
|
||||
|
||||
---
|
||||
|
||||
## What's in a chart
|
||||
|
||||
- A chart must have at least:
|
||||
|
||||
- a `templates` directory, with YAML manifests for Kubernetes resources
|
||||
|
||||
- a `values.yaml` file, containing (tunable) parameters for the chart
|
||||
|
||||
- a `Chart.yaml` file, containing metadata (name, version, description ...)
|
||||
|
||||
- Let's look at a simple chart, `stable/tomcat`
|
||||
|
||||
---
|
||||
|
||||
## Downloading a chart
|
||||
|
||||
- We can use `helm pull` to download a chart from a repo
|
||||
|
||||
.exercise[
|
||||
|
||||
- Download the tarball for `stable/tomcat`:
|
||||
```bash
|
||||
helm pull stable/tomcat
|
||||
```
|
||||
(This will create a file named `tomcat-X.Y.Z.tgz`.)
|
||||
|
||||
- Or, download + untar `stable/tomcat`:
|
||||
```bash
|
||||
helm pull stable/tomcat --untar
|
||||
```
|
||||
(This will create a directory named `tomcat`.)
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Looking at the chart's content
|
||||
|
||||
- Let's look at the files and directories in the `tomcat` chart
|
||||
|
||||
.exercise[
|
||||
|
||||
- Display the tree structure of the chart we just downloaded:
|
||||
```bash
|
||||
tree tomcat
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We see the components mentioned above: `Chart.yaml`, `templates/`, `values.yaml`.
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
- The `templates/` directory contains YAML manifests for Kubernetes resources
|
||||
|
||||
(Deployments, Services, etc.)
|
||||
|
||||
- These manifests can contain template tags
|
||||
|
||||
(using the standard Go template library)
|
||||
|
||||
|
||||
.exercise[
|
||||
|
||||
- Look at the template file for the tomcat Service resource:
|
||||
```bash
|
||||
cat tomcat/templates/appsrv-svc.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Analyzing the template file
|
||||
|
||||
- Tags are identified by `{{ ... }}`
|
||||
|
||||
- `{{ template "x.y" }}` expands a [named template](https://helm.sh/docs/chart_template_guide/named_templates/#declaring-and-using-templates-with-define-and-template)
|
||||
|
||||
(previously defined with `{{ define "x.y "}}...stuff...{{ end }}`)
|
||||
|
||||
- The `.` in `{{ template "x.y" . }}` is the *context* for that named template
|
||||
|
||||
(so that the named template block can access variables from the local context)
|
||||
|
||||
- `{{ .Release.xyz }}` refers to [built-in variables](https://helm.sh/docs/chart_template_guide/builtin_objects/) initialized by Helm
|
||||
|
||||
(indicating the chart name, version, whether we are installing or upgrading ...)
|
||||
|
||||
- `{{ .Values.xyz }}` refers to tunable/settable [values](https://helm.sh/docs/chart_template_guide/values_files/)
|
||||
|
||||
(more on that in a minute)
|
||||
|
||||
---
|
||||
|
||||
## Values
|
||||
|
||||
- Each chart comes with a
|
||||
[values file](https://helm.sh/docs/chart_template_guide/values_files/)
|
||||
|
||||
- It's a YAML file containing a set of default parameters for the chart
|
||||
|
||||
- The values can be accessed in templates with e.g. `{{ .Values.x.y }}`
|
||||
|
||||
(corresponding to field `y` in map `x` in the values file)
|
||||
|
||||
- The values can be set or overridden when installing or ugprading a chart:
|
||||
|
||||
- with `--set x.y=z` (can be used multiple times to set multiple values)
|
||||
|
||||
- with `--values some-yaml-file.yaml` (set a bunch of values from a file)
|
||||
|
||||
- Charts following best practices will have values following specific patterns
|
||||
|
||||
(e.g. having a `service` map allowing to set `service.type` etc.)
|
||||
|
||||
---
|
||||
|
||||
## Other useful tags
|
||||
|
||||
- `{{ if x }} y {{ end }}` allows to include `y` if `x` evaluates to `true`
|
||||
|
||||
(can be used for e.g. healthchecks, annotations, or even an entire resource)
|
||||
|
||||
- `{{ range x }} y {{ end }}` iterates over `x`, evaluating `y` each time
|
||||
|
||||
(the elements of `x` are assigned to `.` in the range scope)
|
||||
|
||||
- `{{- x }}`/`{{ x -}}` will remove whitespace on the left/right
|
||||
|
||||
- The whole [Sprig](http://masterminds.github.io/sprig/) library, with additions:
|
||||
|
||||
`lower` `upper` `quote` `trim` `default` `b64enc` `b64dec` `sha256sum` `indent` `toYaml` ...
|
||||
|
||||
---
|
||||
|
||||
## Pipelines
|
||||
|
||||
- `{{ quote blah }}` can also be expressed as `{{ blah | quote }}`
|
||||
|
||||
- With multiple arguments, `{{ x y z }}` can be expressed as `{{ z | x y }}`)
|
||||
|
||||
- Example: `{{ .Values.annotations | toYaml | indent 4 }}`
|
||||
|
||||
- transforms the map under `annotations` into a YAML string
|
||||
|
||||
- indents it with 4 spaces (to match the surrounding context)
|
||||
|
||||
- Pipelines are not specific to Helm, but a feature of Go templates
|
||||
|
||||
(check the [Go text/template documentation](https://golang.org/pkg/text/template/) for more details and examples)
|
||||
|
||||
---
|
||||
|
||||
## README and NOTES.txt
|
||||
|
||||
- At the top-level of the chart, it's a good idea to have a README
|
||||
|
||||
- It will be viewable with e.g. `helm show readme stable/tomcat`
|
||||
|
||||
- In the `templates/` directory, we can also have a `NOTES.txt` file
|
||||
|
||||
- When the template is installed (or upgraded), `NOTES.txt` is processed too
|
||||
|
||||
(i.e. its `{{ ... }}` tags are evaluated)
|
||||
|
||||
- It gets displayed after the install or upgrade
|
||||
|
||||
- It's a great place to generate messages to tell the user:
|
||||
|
||||
- how to connect to the release they just deployed
|
||||
|
||||
- any passwords or other thing that we generated for them
|
||||
|
||||
---
|
||||
|
||||
## Additional files
|
||||
|
||||
- We can place arbitrary files in the chart (outside of the `templates/` directory)
|
||||
|
||||
- They can be accessed in templates with `.Files`
|
||||
|
||||
- They can be transformed into ConfigMaps or Secrets with `AsConfig` and `AsSecrets`
|
||||
|
||||
(see [this example](https://helm.sh/docs/chart_template_guide/accessing_files/#configmap-and-secrets-utility-functions) in the Helm docs)
|
||||
|
||||
---
|
||||
|
||||
## Hooks and tests
|
||||
|
||||
- We can define *hooks* in our templates
|
||||
|
||||
- Hooks are resources annotated with `"helm.sh/hook": NAME-OF-HOOK`
|
||||
|
||||
- Hook names include `pre-install`, `post-install`, `test`, [and much more](https://helm.sh/docs/topics/charts_hooks/#the-available-hooks)
|
||||
|
||||
- The resources defined in hooks are loaded at a specific time
|
||||
|
||||
- Hook execution is *synchronous*
|
||||
|
||||
(if the resource is a Job or Pod, Helm will wait for its completion)
|
||||
|
||||
- This can be use for database migrations, backups, notifications, smoke tests ...
|
||||
|
||||
- Hooks named `test` are executed only when running `helm test RELEASE-NAME`
|
||||
220
slides/k8s/helm-create-basic-chart.md
Normal file
220
slides/k8s/helm-create-basic-chart.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Creating a basic 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
|
||||
|
||||
- If DockerCoins is not running, see next slide
|
||||
|
||||
.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
|
||||
```
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Obtaining DockerCoins YAML
|
||||
|
||||
- If DockerCoins is not running, we can also obtain the YAML from a public repository
|
||||
|
||||
.exercise[
|
||||
|
||||
- Clone the kubercoins repository:
|
||||
```bash
|
||||
git clone https://github.com/jpetazzo/kubercoins
|
||||
```
|
||||
|
||||
- Copy the YAML files to the `templates/` directory:
|
||||
```bash
|
||||
cp kubercoins/*.yaml dockercoins/templates/
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Testing our helm chart
|
||||
|
||||
.exercise[
|
||||
|
||||
- Let's install our helm chart!
|
||||
```
|
||||
helm install helmcoins dockercoins
|
||||
```
|
||||
(`helmcoins` is the name of the release; `dockercoins` is the local path of the chart)
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
- Since the application is already deployed, this will fail:
|
||||
```
|
||||
Error: rendered manifests contain a resource that already exists.
|
||||
Unable to continue with install: existing resource conflict:
|
||||
kind: Service, namespace: default, name: hasher
|
||||
```
|
||||
|
||||
- To avoid naming conflicts, we will deploy the application in another *namespace*
|
||||
|
||||
---
|
||||
|
||||
## Switching to another namespace
|
||||
|
||||
- We need create a new namespace
|
||||
|
||||
(Helm 2 creates namespaces automatically; Helm 3 doesn't anymore)
|
||||
|
||||
- We need to tell Helm which namespace to use
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create a new namespace:
|
||||
```bash
|
||||
kubectl create namespace helmcoins
|
||||
```
|
||||
|
||||
- Deploy our chart in that namespace:
|
||||
```bash
|
||||
helm install helmcoins dockercoins --namespace=helmcoins
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Helm releases are namespaced
|
||||
|
||||
- Let's try to see the release that we just deployed
|
||||
|
||||
.exercise[
|
||||
|
||||
- List Helm releases:
|
||||
```bash
|
||||
helm list
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Our release doesn't show up!
|
||||
|
||||
We have to specify its namespace (or switch to that namespace).
|
||||
|
||||
---
|
||||
|
||||
## Specifying the namespace
|
||||
|
||||
- Try again, with the correct namespace
|
||||
|
||||
.exercise[
|
||||
|
||||
- List Helm releases in `helmcoins`:
|
||||
```bash
|
||||
helm list --namespace=helmcoins
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## 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=helmcoins
|
||||
```
|
||||
|
||||
- Open it in a web browser
|
||||
|
||||
- Look at the worker logs:
|
||||
```bash
|
||||
kubectl logs deploy/worker --tail=10 --follow --namespace=helmcoins
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Note: it might take a minute or two for the worker to start.
|
||||
|
||||
---
|
||||
|
||||
## Discussion, shortcomings
|
||||
|
||||
- Helm (and Kubernetes) best practices recommend to add a number of annotations
|
||||
|
||||
(e.g. `app.kubernetes.io/name`, `helm.sh/chart`, `app.kubernetes.io/instance` ...)
|
||||
|
||||
- Our basic chart doesn't have any of these
|
||||
|
||||
- Our basic chart doesn't use any template tag
|
||||
|
||||
- Does it make sense to use Helm in that case?
|
||||
|
||||
- *Yes,* because Helm will:
|
||||
|
||||
- track the resources created by the chart
|
||||
|
||||
- save successive revisions, allowing us to rollback
|
||||
|
||||
[Helm docs](https://helm.sh/docs/topics/chart_best_practices/labels/)
|
||||
and [Kubernetes docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/)
|
||||
have details about recommended annotations and labels.
|
||||
|
||||
---
|
||||
|
||||
## Cleaning up
|
||||
|
||||
- Let's remove that chart before moving on
|
||||
|
||||
.exercise[
|
||||
|
||||
- Delete the release (don't forget to specify the namespace):
|
||||
```bash
|
||||
helm delete helmcoins --namespace=helmcoins
|
||||
```
|
||||
|
||||
]
|
||||
579
slides/k8s/helm-create-better-chart.md
Normal file
579
slides/k8s/helm-create-better-chart.md
Normal file
@@ -0,0 +1,579 @@
|
||||
# Creating better Helm charts
|
||||
|
||||
- We are going to create a chart with the helper `helm create`
|
||||
|
||||
- This will give us a chart implementing lots of Helm best practices
|
||||
|
||||
(labels, annotations, structure of the `values.yaml` file ...)
|
||||
|
||||
- We will use that chart as a generic Helm chart
|
||||
|
||||
- We will use it 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
|
||||
|
||||
- In each file, we want to have:
|
||||
```yaml
|
||||
image:
|
||||
repository: IMAGE-REPOSITORY-NAME
|
||||
tag: IMAGE-TAG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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 YAML files for the 5 components, with the following model:
|
||||
```yaml
|
||||
image:
|
||||
repository: `IMAGE-REPOSITORY-NAME` (e.g. dockercoins/worker)
|
||||
tag: `IMAGE-TAG` (e.g. v0.1)
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Deploying DockerCoins components
|
||||
|
||||
- For convenience, let's work in a separate namespace
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create a new namespace (if it doesn't already exist):
|
||||
```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 COMPONENT-NAME CHART-DIRECTORY
|
||||
```
|
||||
|
||||
- We can also use the following command, which is idempotent:
|
||||
```bash
|
||||
helm upgrade COMPONENT-NAME CHART-DIRECTORY --install
|
||||
```
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install the 5 components of DockerCoins:
|
||||
```bash
|
||||
for COMPONENT in hasher redis rng webui worker; do
|
||||
helm upgrade $COMPONENT helmcoins --install --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!
|
||||
|
||||
---
|
||||
|
||||
## Can't pull image
|
||||
|
||||
- It looks like our images can't be found
|
||||
|
||||
.exercise[
|
||||
|
||||
- Use `kubectl describe` on any of the pods in error
|
||||
|
||||
]
|
||||
|
||||
- We're trying to pull `rng:1.16.0` instead of `rng:v0.1`!
|
||||
|
||||
- Where does that `1.16.0` tag come from?
|
||||
|
||||
---
|
||||
|
||||
## Inspecting our template
|
||||
|
||||
- Let's look at the `templates/` directory
|
||||
|
||||
(and try to find the one generating the Deployment resource)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Show the structure of the `helmcoins` chart that Helm generated:
|
||||
```bash
|
||||
tree helmcoins
|
||||
```
|
||||
|
||||
- Check the file `helmcoins/templates/deployment.yaml`
|
||||
|
||||
- Look for the `image:` parameter
|
||||
|
||||
]
|
||||
|
||||
*The image tag references `{{ .Chart.AppVersion }}`. Where does that come from?*
|
||||
|
||||
---
|
||||
|
||||
## The `.Chart` variable
|
||||
|
||||
- `.Chart` is a map corresponding to the values in `Chart.yaml`
|
||||
|
||||
- Let's look for `AppVersion` there!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the file `helmcoins/Chart.yaml`
|
||||
|
||||
- Look for the `appVersion:` parameter
|
||||
|
||||
]
|
||||
|
||||
(Yes, the case is different between the template and the Chart file.)
|
||||
|
||||
---
|
||||
|
||||
## Using the correct tags
|
||||
|
||||
- If we change `AppVersion` to `v0.1`, it will change for *all* deployments
|
||||
|
||||
(including redis)
|
||||
|
||||
- Instead, let's change the *template* to use `{{ .Values.image.tag }}`
|
||||
|
||||
(to match what we've specified in our values YAML files)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit `helmcoins/templates/deployment.yaml`
|
||||
|
||||
- Replace `{{ .Chart.AppVersion }}` with `{{ .Values.image.tag }}`
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Upgrading to use the new template
|
||||
|
||||
- Technically, we just made a new version of the *chart*
|
||||
|
||||
- To use the new template, we need to *upgrade* the release to use that chart
|
||||
|
||||
.exercise[
|
||||
|
||||
- Upgrade all components:
|
||||
```bash
|
||||
for COMPONENT in hasher redis rng webui worker; do
|
||||
helm upgrade $COMPONENT helmcoins
|
||||
done
|
||||
```
|
||||
|
||||
- Check how our pods are doing:
|
||||
```bash
|
||||
kubectl get pods
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should see all pods "Running". But ... not all of them are READY.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting readiness
|
||||
|
||||
- `hasher`, `rng`, `webui` should show up as `1/1 READY`
|
||||
|
||||
- But `redis` and `worker` should show up as `0/1 READY`
|
||||
|
||||
- Why?
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
It's failing both its liveness and readiness probes!
|
||||
|
||||
---
|
||||
|
||||
## 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 remove or 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 false }}` at the beginning (we can change the condition later)
|
||||
|
||||
`{{ end }}` at the end
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit `helmcoins/templates/deployment.yaml`
|
||||
|
||||
- Add `{{ if false }}` on the line before `livenessProbe`
|
||||
|
||||
- Add `{{ end }}` after the `readinessProbe` section
|
||||
|
||||
(see next slide for details)
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
This is what the new YAML should look like (added lines in yellow):
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
`{{ if false }}`
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
`{{ end }}`
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the new chart
|
||||
|
||||
- We need to upgrade all the services again to use the new chart
|
||||
|
||||
.exercise[
|
||||
|
||||
- Upgrade all components:
|
||||
```bash
|
||||
for COMPONENT in hasher redis rng webui worker; do
|
||||
helm upgrade $COMPONENT helmcoins
|
||||
done
|
||||
```
|
||||
|
||||
- Check how our pods are doing:
|
||||
```bash
|
||||
kubectl get pods
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Everything should now be running!
|
||||
|
||||
---
|
||||
|
||||
## What's next?
|
||||
|
||||
- Is this working now?
|
||||
|
||||
.exercise[
|
||||
|
||||
- Let's check the logs of the worker:
|
||||
```bash
|
||||
stern worker
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
This error might look familiar ... The worker can't resolve `redis`.
|
||||
|
||||
Typically, that error means that the `redis` service doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
## Checking services
|
||||
|
||||
- What about the services created by our chart?
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the list of services:
|
||||
```bash
|
||||
kubectl get services
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
They are named `COMPONENT-helmcoins` instead of just `COMPONENT`.
|
||||
|
||||
We need to change that!
|
||||
|
||||
---
|
||||
|
||||
## Where do the service names come from?
|
||||
|
||||
- Look at the YAML template used for the services
|
||||
|
||||
- It should be using `{{ include "helmcoins.fullname" }}`
|
||||
|
||||
- `include` indicates a *template block* defined somewhere else
|
||||
|
||||
.exercise[
|
||||
|
||||
- Find where that `fullname` thing is defined:
|
||||
```bash
|
||||
grep define.*fullname helmcoins/templates/*
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
It should be in `_helpers.tpl`.
|
||||
|
||||
We can look at the definition, but it's fairly complex ...
|
||||
|
||||
---
|
||||
|
||||
## Changing service names
|
||||
|
||||
- Instead of that `{{ include }}` tag, let's use the name of the release
|
||||
|
||||
- The name of the release is available as `{{ .Release.Name }}`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit `helmcoins/templates/service.yaml`
|
||||
|
||||
- Replace the service name with `{{ .Release.Name }}`
|
||||
|
||||
- Upgrade all the releases to use the new chart
|
||||
|
||||
- Confirm that the services now have the right names
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## 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!
|
||||
|
||||
- Let's see how the port number is set
|
||||
|
||||
- We need to look at both the *deployment* template and the *service* template
|
||||
|
||||
---
|
||||
|
||||
## Service template
|
||||
|
||||
- In the service template, we have the following section:
|
||||
```yaml
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
```
|
||||
|
||||
- `port` is the port on which the service is "listening"
|
||||
|
||||
(i.e. to which our code needs to connect)
|
||||
|
||||
- `targetPort` is the port on which the pods are listening
|
||||
|
||||
- The `name` is not important (it's OK if it's `http` even for non-HTTP traffic)
|
||||
|
||||
---
|
||||
|
||||
## Setting the redis port
|
||||
|
||||
- Let's add a `service.port` value to the redis release
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit `redis.yaml` to add:
|
||||
```yaml
|
||||
service:
|
||||
port: 6379
|
||||
```
|
||||
|
||||
- Apply the new values file:
|
||||
```bash
|
||||
helm upgrade redis helmcoins --values=redis.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Deployment template
|
||||
|
||||
- If we look at the deployment template, we see this section:
|
||||
```yaml
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
```
|
||||
|
||||
- The container port is hard-coded to 80
|
||||
|
||||
- We'll change it to use the port number specified in the values
|
||||
|
||||
---
|
||||
|
||||
## Changing the deployment template
|
||||
|
||||
.exercise[
|
||||
|
||||
- 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!
|
||||
419
slides/k8s/helm-intro.md
Normal file
419
slides/k8s/helm-intro.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# Managing stacks with Helm
|
||||
|
||||
- We created our first resources with `kubectl run`, `kubectl expose` ...
|
||||
|
||||
- We have also created resources by loading YAML files with `kubectl apply -f`
|
||||
|
||||
- For larger stacks, managing thousands of lines of YAML is unreasonable
|
||||
|
||||
- These YAML bundles need to be customized with variable parameters
|
||||
|
||||
(E.g.: number of replicas, image version to use ...)
|
||||
|
||||
- It would be nice to have an organized, versioned collection of bundles
|
||||
|
||||
- It would be nice to be able to upgrade/rollback these bundles carefully
|
||||
|
||||
- [Helm](https://helm.sh/) is an open source project offering all these things!
|
||||
|
||||
---
|
||||
|
||||
## Helm concepts
|
||||
|
||||
- `helm` is a CLI tool
|
||||
|
||||
- It is used to find, install, upgrade *charts*
|
||||
|
||||
- A chart is an archive containing templatized YAML bundles
|
||||
|
||||
- Charts are versioned
|
||||
|
||||
- Charts can be stored on private or public repositories
|
||||
|
||||
---
|
||||
|
||||
## Differences between charts and packages
|
||||
|
||||
- A package (deb, rpm...) contains binaries, libraries, etc.
|
||||
|
||||
- A chart contains YAML manifests
|
||||
|
||||
(the binaries, libraries, etc. are in the images referenced by the chart)
|
||||
|
||||
- On most distributions, a package can only be installed once
|
||||
|
||||
(installing another version replaces the installed one)
|
||||
|
||||
- A chart can be installed multiple times
|
||||
|
||||
- Each installation is called a *release*
|
||||
|
||||
- This allows to install e.g. 10 instances of MongoDB
|
||||
|
||||
(with potentially different versions and configurations)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Wait a minute ...
|
||||
|
||||
*But, on my Debian system, I have Python 2 **and** Python 3.
|
||||
<br/>
|
||||
Also, I have multiple versions of the Postgres database engine!*
|
||||
|
||||
Yes!
|
||||
|
||||
But they have different package names:
|
||||
|
||||
- `python2.7`, `python3.8`
|
||||
|
||||
- `postgresql-10`, `postgresql-11`
|
||||
|
||||
Good to know: the Postgres package in Debian includes
|
||||
provisions to deploy multiple Postgres servers on the
|
||||
same system, but it's an exception (and it's a lot of
|
||||
work done by the package maintainer, not by the `dpkg`
|
||||
or `apt` tools).
|
||||
|
||||
---
|
||||
|
||||
## Helm 2 vs Helm 3
|
||||
|
||||
- Helm 3 was released [November 13, 2019](https://helm.sh/blog/helm-3-released/)
|
||||
|
||||
- Charts remain compatible between Helm 2 and Helm 3
|
||||
|
||||
- The CLI is very similar (with minor changes to some commands)
|
||||
|
||||
- The main difference is that Helm 2 uses `tiller`, a server-side component
|
||||
|
||||
- Helm 3 doesn't use `tiller` at all, making it simpler (yay!)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## With or without `tiller`
|
||||
|
||||
- With Helm 3:
|
||||
|
||||
- the `helm` CLI communicates directly with the Kubernetes API
|
||||
|
||||
- it creates resources (deployments, services...) with our credentials
|
||||
|
||||
- With Helm 2:
|
||||
|
||||
- the `helm` CLI communicates with `tiller`, telling `tiller` what to do
|
||||
|
||||
- `tiller` then communicates with the Kubernetes API, using its own credentials
|
||||
|
||||
- This indirect model caused significant permissions headaches
|
||||
|
||||
(`tiller` required very broad permissions to function)
|
||||
|
||||
- `tiller` was removed in Helm 3 to simplify the security aspects
|
||||
|
||||
---
|
||||
|
||||
## Installing Helm
|
||||
|
||||
- If the `helm` CLI is not installed in your environment, install it
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check if `helm` is installed:
|
||||
```bash
|
||||
helm
|
||||
```
|
||||
|
||||
- If it's not installed, run the following command:
|
||||
```bash
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get-helm-3 \
|
||||
| bash
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(To install Helm 2, replace `get-helm-3` with `get`.)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Only if using Helm 2 ...
|
||||
|
||||
- We need to install Tiller and give it some permissions
|
||||
|
||||
- Tiller is composed of a *service* and a *deployment* in the `kube-system` namespace
|
||||
|
||||
- They can be managed (installed, upgraded...) with the `helm` CLI
|
||||
|
||||
.exercise[
|
||||
|
||||
- Deploy Tiller:
|
||||
```bash
|
||||
helm init
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
At the end of the install process, you will see:
|
||||
|
||||
```
|
||||
Happy Helming!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Only if using Helm 2 ...
|
||||
|
||||
- Tiller needs permissions to create Kubernetes resources
|
||||
|
||||
- In a more realistic deployment, you might create per-user or per-team
|
||||
service accounts, roles, and role bindings
|
||||
|
||||
.exercise[
|
||||
|
||||
- Grant `cluster-admin` role to `kube-system:default` service account:
|
||||
```bash
|
||||
kubectl create clusterrolebinding add-on-cluster-admin \
|
||||
--clusterrole=cluster-admin --serviceaccount=kube-system:default
|
||||
```
|
||||
|
||||
|
||||
]
|
||||
|
||||
(Defining the exact roles and permissions on your cluster requires
|
||||
a deeper knowledge of Kubernetes' RBAC model. The command above is
|
||||
fine for personal and development clusters.)
|
||||
|
||||
---
|
||||
|
||||
## Charts and repositories
|
||||
|
||||
- A *repository* (or repo in short) is a collection of charts
|
||||
|
||||
- It's just a bunch of files
|
||||
|
||||
(they can be hosted by a static HTTP server, or on a local directory)
|
||||
|
||||
- We can add "repos" to Helm, giving them a nickname
|
||||
|
||||
- The nickname is used when referring to charts on that repo
|
||||
|
||||
(for instance, if we try to install `hello/world`, that
|
||||
means the chart `world` on the repo `hello`; and that repo
|
||||
`hello` might be something like https://blahblah.hello.io/charts/)
|
||||
|
||||
---
|
||||
|
||||
## Managing repositories
|
||||
|
||||
- Let's check what repositories we have, and add the `stable` repo
|
||||
|
||||
(the `stable` repo contains a set of official-ish charts)
|
||||
|
||||
.exercise[
|
||||
|
||||
- List our repos:
|
||||
```bash
|
||||
helm repo list
|
||||
```
|
||||
|
||||
- Add the `stable` repo:
|
||||
```bash
|
||||
helm repo add stable https://kubernetes-charts.storage.googleapis.com/
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Adding a repo can take a few seconds (it downloads the list of charts from the repo).
|
||||
|
||||
It's OK to add a repo that already exists (it will merely update it).
|
||||
|
||||
---
|
||||
|
||||
## Search available charts
|
||||
|
||||
- We can search available charts with `helm search`
|
||||
|
||||
- We need to specify where to search (only our repos, or Helm Hub)
|
||||
|
||||
- Let's search for all charts mentioning tomcat!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Search for tomcat in the repo that we added earlier:
|
||||
```bash
|
||||
helm search repo tomcat
|
||||
```
|
||||
|
||||
- Search for tomcat on the Helm Hub:
|
||||
```bash
|
||||
helm search hub tomcat
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
[Helm Hub](https://hub.helm.sh/) indexes many repos, using the [Monocular](https://github.com/helm/monocular) server.
|
||||
|
||||
---
|
||||
|
||||
## Charts and releases
|
||||
|
||||
- "Installing a chart" means creating a *release*
|
||||
|
||||
- We need to name that release
|
||||
|
||||
(or use the `--generate-name` to get Helm to generate one for us)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install the tomcat chart that we found earlier:
|
||||
```bash
|
||||
helm install java4ever stable/tomcat
|
||||
```
|
||||
|
||||
- List the releases:
|
||||
```bash
|
||||
helm list
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Searching and installing with Helm 2
|
||||
|
||||
- Helm 2 doesn't have support for the Helm Hub
|
||||
|
||||
- The `helm search` command only takes a search string argument
|
||||
|
||||
(e.g. `helm search tomcat`)
|
||||
|
||||
- With Helm 2, the name is optional:
|
||||
|
||||
`helm install stable/tomcat` will automatically generate a name
|
||||
|
||||
`helm install --name java4ever stable/tomcat` will specify a name
|
||||
|
||||
---
|
||||
|
||||
## Viewing resources of a release
|
||||
|
||||
- This specific chart labels all its resources with a `release` label
|
||||
|
||||
- We can use a selector to see these resources
|
||||
|
||||
.exercise[
|
||||
|
||||
- List all the resources created by this release:
|
||||
```bash
|
||||
kuectl get all --selector=release=java4ever
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Note: this `release` label wasn't added automatically by Helm.
|
||||
<br/>
|
||||
It is defined in that chart. In other words, not all charts will provide this label.
|
||||
|
||||
---
|
||||
|
||||
## Configuring a release
|
||||
|
||||
- By default, `stable/tomcat` creates a service of type `LoadBalancer`
|
||||
|
||||
- We would like to change that to a `NodePort`
|
||||
|
||||
- We could use `kubectl edit service java4ever-tomcat`, but ...
|
||||
|
||||
... our changes would get overwritten next time we update that chart!
|
||||
|
||||
- Instead, we are going to *set a value*
|
||||
|
||||
- Values are parameters that the chart can use to change its behavior
|
||||
|
||||
- Values have default values
|
||||
|
||||
- Each chart is free to define its own values and their defaults
|
||||
|
||||
---
|
||||
|
||||
## Checking possible values
|
||||
|
||||
- We can inspect a chart with `helm show` or `helm inspect`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Look at the README for tomcat:
|
||||
```bash
|
||||
helm show readme stable/tomcat
|
||||
```
|
||||
|
||||
- Look at the values and their defaults:
|
||||
```bash
|
||||
helm show values stable/tomcat
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The `values` may or may not have useful comments.
|
||||
|
||||
The `readme` may or may not have (accurate) explanations for the values.
|
||||
|
||||
(If we're unlucky, there won't be any indication about how to use the values!)
|
||||
|
||||
---
|
||||
|
||||
## Setting values
|
||||
|
||||
- Values can be set when installing a chart, or when upgrading it
|
||||
|
||||
- We are going to update `java4ever` to change the type of the service
|
||||
|
||||
.exercise[
|
||||
|
||||
- Update `java4ever`:
|
||||
```bash
|
||||
helm upgrade java4ever stable/tomcat --set service.type=NodePort
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Note that we have to specify the chart that we use (`stable/tomcat`),
|
||||
even if we just want to update some values.
|
||||
|
||||
We can set multiple values. If we want to set many values, we can use `-f`/`--values` and pass a YAML file with all the values.
|
||||
|
||||
All unspecified values will take the default values defined in the chart.
|
||||
|
||||
---
|
||||
|
||||
## Connecting to tomcat
|
||||
|
||||
- Let's check the tomcat server that we just installed
|
||||
|
||||
- Note: its readiness probe has a 60s delay
|
||||
|
||||
(so it will take 60s after the initial deployment before the service works)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the node port allocated to the service:
|
||||
```bash
|
||||
kubectl get service java4ever-tomcat
|
||||
PORT=$(kubectl get service java4ever-tomcat -o jsonpath={..nodePort})
|
||||
```
|
||||
|
||||
- Connect to it, checking the demo app on `/sample/`:
|
||||
```bash
|
||||
curl localhost:$PORT/sample/
|
||||
```
|
||||
|
||||
]
|
||||
234
slides/k8s/helm-secrets.md
Normal file
234
slides/k8s/helm-secrets.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Helm secrets
|
||||
|
||||
- Helm can do *rollbacks*:
|
||||
|
||||
- to previously installed charts
|
||||
|
||||
- to previous sets of values
|
||||
|
||||
- How and where does it store the data needed to do that?
|
||||
|
||||
- Let's investigate!
|
||||
|
||||
---
|
||||
|
||||
## We need a release
|
||||
|
||||
- We need to install something with Helm
|
||||
|
||||
- Let's use the `stable/tomcat` chart as an example
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install a release called `tomcat` with the chart `stable/tomcat`:
|
||||
```bash
|
||||
helm upgrade tomcat stable/tomcat --install
|
||||
```
|
||||
|
||||
- Let's upgrade that release, and change a value:
|
||||
```bash
|
||||
helm upgrade tomcat stable/tomcat --set ingress.enabled=true
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Release history
|
||||
|
||||
- Helm stores successive revisions of each release
|
||||
|
||||
.exercise[
|
||||
|
||||
- View the history for that release:
|
||||
```bash
|
||||
helm history tomcat
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Where does that come from?
|
||||
|
||||
---
|
||||
|
||||
## Investigate
|
||||
|
||||
- Possible options:
|
||||
|
||||
- local filesystem (no, because history is visible from other machines)
|
||||
|
||||
- persistent volumes (no, Helm works even without them)
|
||||
|
||||
- ConfigMaps, Secrets?
|
||||
|
||||
.exercise[
|
||||
|
||||
- Look for ConfigMaps and Secrets:
|
||||
```bash
|
||||
kuebectl get configmaps,secrets
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
We should see a number of secrets with TYPE `helm.sh/release.v1`.
|
||||
|
||||
---
|
||||
|
||||
## Unpacking a secret
|
||||
|
||||
- Let's find out what is in these Helm secrets
|
||||
|
||||
.exercise[
|
||||
|
||||
- Examine the secret corresponding to the second release of `tomcat`:
|
||||
```bash
|
||||
kubectl describe secret sh.helm.release.v1.tomcat.v2
|
||||
```
|
||||
(`v1` is the secret format; `v2` means revision 2 of the `tomcat` release)
|
||||
|
||||
]
|
||||
|
||||
There is a key named `release`.
|
||||
|
||||
---
|
||||
|
||||
## Unpacking the release data
|
||||
|
||||
- Let's see what's in this `release` thing!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Dump the secret:
|
||||
```bash
|
||||
kubectl get secret sh.helm.release.v1.tomcat.v2 \
|
||||
-o go-template='{{ .data.release }}'
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Secrets are encoded in base64. We need to decode that!
|
||||
|
||||
---
|
||||
|
||||
## Decoding base64
|
||||
|
||||
- We can pipe the output through `base64 -d` or use go-template's `base64decode`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Decode the secret:
|
||||
```bash
|
||||
kubectl get secret sh.helm.release.v1.tomcat.v2 \
|
||||
-o go-template='{{ .data.release | base64decode }}'
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
... Wait, this *still* looks like base64. What's going on?
|
||||
|
||||
--
|
||||
|
||||
Let's try one more round of decoding!
|
||||
|
||||
---
|
||||
|
||||
## Decoding harder
|
||||
|
||||
- Just add one more base64 decode filter
|
||||
|
||||
.exercise[
|
||||
|
||||
- Decode it twice:
|
||||
```bash
|
||||
kubectl get secret sh.helm.release.v1.tomcat.v2 \
|
||||
-o go-template='{{ .data.release | base64decode | base64decode }}'
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
... OK, that was *a lot* of binary data. What sould we do with it?
|
||||
|
||||
---
|
||||
|
||||
## Guessing data type
|
||||
|
||||
- We could use `file` to figure out the data type
|
||||
|
||||
.exercise[
|
||||
|
||||
- Pipe the decoded release through `file -`:
|
||||
```bash
|
||||
kubectl get secret sh.helm.release.v1.tomcat.v2 \
|
||||
-o go-template='{{ .data.release | base64decode | base64decode }}' \
|
||||
| file -
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
Gzipped data! It can be decoded with `gunzip -c`.
|
||||
|
||||
---
|
||||
|
||||
## Uncompressing the data
|
||||
|
||||
- Let's uncompress the data and save it to a file
|
||||
|
||||
.exercise[
|
||||
|
||||
- Rerun the previous command, but with `| gunzip -c > release-info` :
|
||||
```bash
|
||||
kubectl get secret sh.helm.release.v1.tomcat.v2 \
|
||||
-o go-template='{{ .data.release | base64decode | base64decode }}' \
|
||||
| gunzip -c > release-info
|
||||
```
|
||||
|
||||
- Look at `release-info`:
|
||||
```bash
|
||||
cat release-info
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
It's a bundle of ~~YAML~~ JSON.
|
||||
|
||||
---
|
||||
|
||||
## Looking at the JSON
|
||||
|
||||
If we inspect that JSON (e.g. with `jq keys release-info`), we see:
|
||||
|
||||
- `chart` (contains the entire chart used for that release)
|
||||
- `config` (contains the values that we've set)
|
||||
- `info` (date of deployment, status messages)
|
||||
- `manifest` (YAML generated from the templates)
|
||||
- `name` (name of the release, so `tomcat`)
|
||||
- `namespace` (namespace where we deployed the release)
|
||||
- `version` (revision number within that release; starts at 1)
|
||||
|
||||
The chart is in a structured format, but it's entirely captured in this JSON.
|
||||
|
||||
---
|
||||
|
||||
## Conclusions
|
||||
|
||||
- Helm stores each release information in a Secret in the namespace of the release
|
||||
|
||||
- The secret is JSON object (gzipped and encoded in base64)
|
||||
|
||||
- It contains the manifests generated for that release
|
||||
|
||||
- ... And everything needed to rebuild these manifests
|
||||
|
||||
(including the full source of the chart, and the values used)
|
||||
|
||||
- This allows arbitrary rollbacks, as well as tweaking values even without having access to the source of the chart (or the chart repo) used for deployment
|
||||
@@ -1,178 +0,0 @@
|
||||
# Managing stacks with Helm
|
||||
|
||||
- We created our first resources with `kubectl run`, `kubectl expose` ...
|
||||
|
||||
- We have also created resources by loading YAML files with `kubectl apply -f`
|
||||
|
||||
- For larger stacks, managing thousands of lines of YAML is unreasonable
|
||||
|
||||
- These YAML bundles need to be customized with variable parameters
|
||||
|
||||
(E.g.: number of replicas, image version to use ...)
|
||||
|
||||
- It would be nice to have an organized, versioned collection of bundles
|
||||
|
||||
- It would be nice to be able to upgrade/rollback these bundles carefully
|
||||
|
||||
- [Helm](https://helm.sh/) is an open source project offering all these things!
|
||||
|
||||
---
|
||||
|
||||
## Helm concepts
|
||||
|
||||
- `helm` is a CLI tool
|
||||
|
||||
- `tiller` is its companion server-side component
|
||||
|
||||
- A "chart" is an archive containing templatized YAML bundles
|
||||
|
||||
- Charts are versioned
|
||||
|
||||
- Charts can be stored on private or public repositories
|
||||
|
||||
---
|
||||
|
||||
## Installing Helm
|
||||
|
||||
- If the `helm` CLI is not installed in your environment, install it
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check if `helm` is installed:
|
||||
```bash
|
||||
helm
|
||||
```
|
||||
|
||||
- If it's not installed, run the following command:
|
||||
```bash
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Installing Tiller
|
||||
|
||||
- Tiller is composed of a *service* and a *deployment* in the `kube-system` namespace
|
||||
|
||||
- They can be managed (installed, upgraded...) with the `helm` CLI
|
||||
|
||||
.exercise[
|
||||
|
||||
- Deploy Tiller:
|
||||
```bash
|
||||
helm init
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
If Tiller was already installed, don't worry: this won't break it.
|
||||
|
||||
At the end of the install process, you will see:
|
||||
|
||||
```
|
||||
Happy Helming!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fix account permissions
|
||||
|
||||
- Helm permission model requires us to tweak permissions
|
||||
|
||||
- In a more realistic deployment, you might create per-user or per-team
|
||||
service accounts, roles, and role bindings
|
||||
|
||||
.exercise[
|
||||
|
||||
- Grant `cluster-admin` role to `kube-system:default` service account:
|
||||
```bash
|
||||
kubectl create clusterrolebinding add-on-cluster-admin \
|
||||
--clusterrole=cluster-admin --serviceaccount=kube-system:default
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(Defining the exact roles and permissions on your cluster requires
|
||||
a deeper knowledge of Kubernetes' RBAC model. The command above is
|
||||
fine for personal and development clusters.)
|
||||
|
||||
---
|
||||
|
||||
## View available charts
|
||||
|
||||
- A public repo is pre-configured when installing Helm
|
||||
|
||||
- We can view available charts with `helm search` (and an optional keyword)
|
||||
|
||||
.exercise[
|
||||
|
||||
- View all available charts:
|
||||
```bash
|
||||
helm search
|
||||
```
|
||||
|
||||
- View charts related to `prometheus`:
|
||||
```bash
|
||||
helm search prometheus
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Install a chart
|
||||
|
||||
- Most charts use `LoadBalancer` service types by default
|
||||
|
||||
- Most charts require persistent volumes to store data
|
||||
|
||||
- We need to relax these requirements a bit
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install the Prometheus metrics collector on our cluster:
|
||||
```bash
|
||||
helm install stable/prometheus \
|
||||
--set server.service.type=NodePort \
|
||||
--set server.persistentVolume.enabled=false
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Where do these `--set` options come from?
|
||||
|
||||
---
|
||||
|
||||
## Inspecting a chart
|
||||
|
||||
- `helm inspect` shows details about a chart (including available options)
|
||||
|
||||
.exercise[
|
||||
|
||||
- See the metadata and all available options for `stable/prometheus`:
|
||||
```bash
|
||||
helm inspect stable/prometheus
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The chart's metadata includes a URL to the project's home page.
|
||||
|
||||
(Sometimes it conveniently points to the documentation for the chart.)
|
||||
|
||||
---
|
||||
|
||||
## Viewing installed charts
|
||||
|
||||
- Helm keeps track of what we've installed
|
||||
|
||||
.exercise[
|
||||
|
||||
- List installed Helm charts:
|
||||
```bash
|
||||
helm list
|
||||
```
|
||||
|
||||
]
|
||||
@@ -105,19 +105,36 @@
|
||||
|
||||
- Monitor pod CPU usage:
|
||||
```bash
|
||||
watch kubectl top pods
|
||||
watch kubectl top pods -l app=busyhttp
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait NAME```
|
||||
```tmux split-pane -v```
|
||||
```bash CLUSTERIP=$(kubectl get svc busyhttp -o jsonpath={.spec.clusterIP})```
|
||||
-->
|
||||
|
||||
- Monitor service latency:
|
||||
```bash
|
||||
httping http://`ClusterIP`/
|
||||
httping http://`$CLUSTERIP`/
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait connected to```
|
||||
```tmux split-pane -v```
|
||||
-->
|
||||
|
||||
- Monitor cluster events:
|
||||
```bash
|
||||
kubectl get events -w
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait Normal```
|
||||
```tmux split-pane -v```
|
||||
```bash CLUSTERIP=$(kubectl get svc busyhttp -o jsonpath={.spec.clusterIP})```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
@@ -130,9 +147,15 @@
|
||||
|
||||
- Send a lot of requests to the service, with a concurrency level of 3:
|
||||
```bash
|
||||
ab -c 3 -n 100000 http://`ClusterIP`/
|
||||
ab -c 3 -n 100000 http://`$CLUSTERIP`/
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait be patient```
|
||||
```tmux split-pane -v```
|
||||
```tmux selectl even-vertical```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
The latency (reported by `httping`) should increase above 3s.
|
||||
@@ -193,6 +216,20 @@ This can also be set with `--cpu-percent=`.
|
||||
kubectl edit deployment busyhttp
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait Please edit```
|
||||
```keys /resources```
|
||||
```key ^J```
|
||||
```keys $xxxo requests:```
|
||||
```key ^J```
|
||||
```key Space```
|
||||
```key Space```
|
||||
```keys cpu: "1"```
|
||||
```key Escape```
|
||||
```keys :wq```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
- In the `containers` list, add the following block:
|
||||
```yaml
|
||||
resources:
|
||||
@@ -243,3 +280,29 @@ This can also be set with `--cpu-percent=`.
|
||||
- The metrics provided by metrics server are standard; everything else is custom
|
||||
|
||||
- For more details, see [this great blog post](https://medium.com/uptime-99/kubernetes-hpa-autoscaling-with-custom-and-external-metrics-da7f41ff7846) or [this talk](https://www.youtube.com/watch?v=gSiGFH4ZnS8)
|
||||
|
||||
---
|
||||
|
||||
## Cleanup
|
||||
|
||||
- Since `busyhttp` uses CPU cycles, let's stop it before moving on
|
||||
|
||||
.exercise[
|
||||
|
||||
- Delete the `busyhttp` Deployment:
|
||||
```bash
|
||||
kubectl delete deployment busyhttp
|
||||
```
|
||||
|
||||
<!--
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -524,3 +524,183 @@ spec:
|
||||
- This should eventually stabilize
|
||||
|
||||
(remember that ingresses are currently `apiVersion: networking.k8s.io/v1beta1`)
|
||||
|
||||
---
|
||||
|
||||
## A special feature in action
|
||||
|
||||
- We're going to see how to implement *canary releases* with Traefik
|
||||
|
||||
- This feature is available on multiple ingress controllers
|
||||
|
||||
- ... But it is configured very differently on each of them
|
||||
|
||||
---
|
||||
|
||||
## Canary releases
|
||||
|
||||
- A *canary release* (or canary launch or canary deployment) is a release that will process only a small fraction of the workload
|
||||
|
||||
- After deploying the canary, we compare its metrics to the normal release
|
||||
|
||||
- If the metrics look good, the canary will progressively receive more traffic
|
||||
|
||||
(until it gets 100% and becomes the new normal release)
|
||||
|
||||
- If the metrics aren't good, the canary is automatically removed
|
||||
|
||||
- When we deploy a bad release, only a tiny fraction of traffic is affected
|
||||
|
||||
---
|
||||
|
||||
## Various ways to implement canary
|
||||
|
||||
- Example 1: canary for a microservice
|
||||
|
||||
- 1% of all requests (sampled randomly) are sent to the canary
|
||||
- the remaining 99% are sent to the normal release
|
||||
|
||||
- Example 2: canary for a web app
|
||||
|
||||
- 1% of users are sent to the canary web site
|
||||
- the remaining 99% are sent to the normal release
|
||||
|
||||
- Example 3: canary for shipping physical goods
|
||||
|
||||
- 1% of orders are shipped with the canary process
|
||||
- the reamining 99% are shipped with the normal process
|
||||
|
||||
- We're going to implement example 1 (per-request routing)
|
||||
|
||||
---
|
||||
|
||||
## Canary releases with Traefik
|
||||
|
||||
- We need to deploy the canary and expose it with a separate service
|
||||
|
||||
- Then, in the Ingress resource, we need:
|
||||
|
||||
- multiple `paths` entries (one for each service, canary and normal)
|
||||
|
||||
- an extra annotation indicating the weight of each service
|
||||
|
||||
- If we want, we can send requests to more than 2 services
|
||||
|
||||
- Let's send requests to our 3 cheesy services!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the resource shown on the next slide
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## The Ingress resource
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheeseplate
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/service-weights: |
|
||||
cheddar: 50%
|
||||
wensleydale: 25%
|
||||
stilton: 25%
|
||||
spec:
|
||||
rules:
|
||||
- host: cheeseplate.`A.B.C.D`.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: 80
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: wensledale
|
||||
servicePort: 80
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: stilton
|
||||
servicePort: 80
|
||||
```
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Testing the canary
|
||||
|
||||
- Let's check the percentage of requests going to each service
|
||||
|
||||
.exercise[
|
||||
|
||||
- Continuously send HTTP requests to the new ingress:
|
||||
```bash
|
||||
while sleep 0.1; do
|
||||
curl -s http://cheeseplate.A.B.C.D.nip.io/
|
||||
done
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should see a 50/25/25 request mix.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Load balancing fairness
|
||||
|
||||
Note: if we use odd request ratios, the load balancing algorithm might appear to be broken on a small scale (when sending a small number of requests), but on a large scale (with many requests) it will be fair.
|
||||
|
||||
For instance, with a 11%/89% ratio, we can see 79 requests going to the 89%-weighted service, and then requests alternating between the two services; then 79 requests again, etc.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Other ingress controllers
|
||||
|
||||
*Just to illustrate how different things are ...*
|
||||
|
||||
- With the NGINX ingress controller:
|
||||
|
||||
- define two ingress ressources
|
||||
<br/>
|
||||
(specifying rules with the same host+path)
|
||||
|
||||
- add `nginx.ingress.kubernetes.io/canary` annotations on each
|
||||
|
||||
|
||||
- With Linkerd2:
|
||||
|
||||
- define two services
|
||||
|
||||
- define an extra service for the weighted aggregate of the two
|
||||
|
||||
- define a TrafficSplit (this is a CRD introduced by the SMI spec)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## We need more than that
|
||||
|
||||
What we saw is just one of the multiple building blocks that we need to achieve a canary release.
|
||||
|
||||
We also need:
|
||||
|
||||
- metrics (latency, performance ...) for our releases
|
||||
|
||||
- automation to alter canary weights
|
||||
|
||||
(increase canary weight if metrics look good; decrease otherwise)
|
||||
|
||||
- a mechanism to manage the lifecycle of the canary releases
|
||||
|
||||
(create them, promote them, delete them ...)
|
||||
|
||||
For inspiration, check [flagger by Weave](https://github.com/weaveworks/flagger).
|
||||
|
||||
@@ -14,42 +14,80 @@
|
||||
|
||||
`ClusterIP`, `NodePort`, `LoadBalancer`, `ExternalName`
|
||||
|
||||
---
|
||||
|
||||
## Basic service types
|
||||
|
||||
- `ClusterIP` (default type)
|
||||
|
||||
- a virtual IP address is allocated for the service (in an internal, private range)
|
||||
- this IP address is reachable only from within the cluster (nodes and pods)
|
||||
- our code can connect to the service using the original port number
|
||||
|
||||
- `NodePort`
|
||||
|
||||
- a port is allocated for the service (by default, in the 30000-32768 range)
|
||||
- that port is made available *on all our nodes* and anybody can connect to it
|
||||
- our code must be changed to connect to that new port number
|
||||
|
||||
These service types are always available.
|
||||
|
||||
Under the hood: `kube-proxy` is using a userland proxy and a bunch of `iptables` rules.
|
||||
- HTTP services can also use `Ingress` resources (more on that later)
|
||||
|
||||
---
|
||||
|
||||
## More service types
|
||||
## `ClusterIP`
|
||||
|
||||
- `LoadBalancer`
|
||||
- It's the default service type
|
||||
|
||||
- an external load balancer is allocated for the service
|
||||
- the load balancer is configured accordingly
|
||||
<br/>(e.g.: a `NodePort` service is created, and the load balancer sends traffic to that port)
|
||||
- available only when the underlying infrastructure provides some "load balancer as a service"
|
||||
<br/>(e.g. AWS, Azure, GCE, OpenStack...)
|
||||
- A virtual IP address is allocated for the service
|
||||
|
||||
- `ExternalName`
|
||||
(in an internal, private range; e.g. 10.96.0.0/12)
|
||||
|
||||
- the DNS entry managed by CoreDNS will just be a `CNAME` to a provided record
|
||||
- no port, no IP address, no nothing else is allocated
|
||||
- This IP address is reachable only from within the cluster (nodes and pods)
|
||||
|
||||
- Our code can connect to the service using the original port number
|
||||
|
||||
- Perfect for internal communication, within the cluster
|
||||
|
||||
---
|
||||
|
||||
## `LoadBalancer`
|
||||
|
||||
- An external load balancer is allocated for the service
|
||||
|
||||
(typically a cloud load balancer, e.g. ELB on AWS, GLB on GCE ...)
|
||||
|
||||
- This is available only when the underlying infrastructure provides some kind of
|
||||
"load balancer as a service"
|
||||
|
||||
- Each service of that type will typically cost a little bit of money
|
||||
|
||||
(e.g. a few cents per hour on AWS or GCE)
|
||||
|
||||
- Ideally, traffic would flow directly from the load balancer to the pods
|
||||
|
||||
- In practice, it will often flow through a `NodePort` first
|
||||
|
||||
---
|
||||
|
||||
## `NodePort`
|
||||
|
||||
- A port number is allocated for the service
|
||||
|
||||
(by default, in the 30000-32767 range)
|
||||
|
||||
- That port is made available *on all our nodes* and anybody can connect to it
|
||||
|
||||
(we can connect to any node on that port to reach the service)
|
||||
|
||||
- Our code needs to be changed to connect to that new port number
|
||||
|
||||
- Under the hood: `kube-proxy` sets up a bunch of `iptables` rules on our nodes
|
||||
|
||||
- Sometimes, it's the only available option for external traffic
|
||||
|
||||
(e.g. most clusters deployed with kubeadm or on-premises)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## `ExternalName`
|
||||
|
||||
- No load balancer (internal or external) is created
|
||||
|
||||
- Only a DNS entry gets added to the DNS managed by Kubernetes
|
||||
|
||||
- That DNS entry will just be a `CNAME` to a provided record
|
||||
|
||||
Example:
|
||||
```bash
|
||||
kubectl create service externalname k8s --external-name kubernetes.io
|
||||
```
|
||||
*Creates a CNAME `k8s` pointing to `kubernetes.io`*
|
||||
|
||||
---
|
||||
|
||||
@@ -86,7 +124,10 @@ Under the hood: `kube-proxy` is using a userland proxy and a bunch of `iptables`
|
||||
kubectl get pods -w
|
||||
```
|
||||
|
||||
<!-- ```keys ^C``` -->
|
||||
<!--
|
||||
```wait NAME```
|
||||
```tmux split-pane -h```
|
||||
-->
|
||||
|
||||
- Create a deployment for this very lightweight HTTP server:
|
||||
```bash
|
||||
@@ -153,6 +194,8 @@ Under the hood: `kube-proxy` is using a userland proxy and a bunch of `iptables`
|
||||
|
||||
<!--
|
||||
```hide kubectl wait deploy httpenv --for condition=available```
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- Send a few requests:
|
||||
@@ -279,18 +322,28 @@ error: the server doesn't have a resource type "endpoint"
|
||||
|
||||
---
|
||||
|
||||
## Exposing services to the outside world
|
||||
class: extra-details
|
||||
|
||||
- The default type (ClusterIP) only works for internal traffic
|
||||
## `ExternalIP`
|
||||
|
||||
- If we want to accept external traffic, we can use one of these:
|
||||
- When creating a servivce, we can also specify an `ExternalIP`
|
||||
|
||||
- NodePort (expose a service on a TCP port between 30000-32768)
|
||||
(this is not a type, but an extra attribute to the service)
|
||||
|
||||
- LoadBalancer (provision a cloud load balancer for our service)
|
||||
- It will make the service availableon this IP address
|
||||
|
||||
- ExternalIP (use one node's external IP address)
|
||||
(if the IP address belongs to a node of the cluster)
|
||||
|
||||
- Ingress (a special mechanism for HTTP services)
|
||||
---
|
||||
|
||||
*We'll see NodePorts and Ingresses more in detail later.*
|
||||
## `Ingress`
|
||||
|
||||
- Ingresses are another type (kind) of resource
|
||||
|
||||
- They are specifically for HTTP services
|
||||
|
||||
(not TCP or UDP)
|
||||
|
||||
- They can also handle TLS certificates, URL rewriting ...
|
||||
|
||||
- They require an *Ingress Controller* to function
|
||||
|
||||
@@ -20,6 +20,50 @@
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## `kubectl` is the new SSH
|
||||
|
||||
- We often start managing servers with SSH
|
||||
|
||||
(installing packages, troubleshooting ...)
|
||||
|
||||
- At scale, it becomes tedious, repetitive, error-prone
|
||||
|
||||
- Instead, we use config management, central logging, etc.
|
||||
|
||||
- In many cases, we still need SSH:
|
||||
|
||||
- as the underlying access method (e.g. Ansible)
|
||||
|
||||
- to debug tricky scenarios
|
||||
|
||||
- to inspect and poke at things
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## The parallel with `kubectl`
|
||||
|
||||
- We often start managing Kubernetes clusters with `kubectl`
|
||||
|
||||
(deploying applications, troubleshooting ...)
|
||||
|
||||
- At scale (with many applications or clusters), it becomes tedious, repetitive, error-prone
|
||||
|
||||
- Instead, we use automated pipelines, observability tooling, etc.
|
||||
|
||||
- In many cases, we still need `kubectl`:
|
||||
|
||||
- to debug tricky scenarios
|
||||
|
||||
- to inspect and poke at things
|
||||
|
||||
- The Kubernetes API is always the underlying access method
|
||||
|
||||
---
|
||||
|
||||
## `kubectl get`
|
||||
|
||||
- Let's look at our `Node` resources with `kubectl get`!
|
||||
@@ -71,7 +115,7 @@
|
||||
|
||||
- Show the capacity of all our nodes as a stream of JSON objects:
|
||||
```bash
|
||||
kubectl get nodes -o json |
|
||||
kubectl get nodes -o json |
|
||||
jq ".items[] | {name:.metadata.name} + .status.capacity"
|
||||
```
|
||||
|
||||
@@ -182,53 +226,6 @@ class: extra-details
|
||||
|
||||
---
|
||||
|
||||
## Services
|
||||
|
||||
- A *service* is a stable endpoint to connect to "something"
|
||||
|
||||
(In the initial proposal, they were called "portals")
|
||||
|
||||
.exercise[
|
||||
|
||||
- List the services on our cluster with one of these commands:
|
||||
```bash
|
||||
kubectl get services
|
||||
kubectl get svc
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
There is already one service on our cluster: the Kubernetes API itself.
|
||||
|
||||
---
|
||||
|
||||
## ClusterIP services
|
||||
|
||||
- A `ClusterIP` service is internal, available from the cluster only
|
||||
|
||||
- This is useful for introspection from within containers
|
||||
|
||||
.exercise[
|
||||
|
||||
- Try to connect to the API:
|
||||
```bash
|
||||
curl -k https://`10.96.0.1`
|
||||
```
|
||||
|
||||
- `-k` is used to skip certificate verification
|
||||
|
||||
- Make sure to replace 10.96.0.1 with the CLUSTER-IP shown by `kubectl get svc`
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
The error that we see is expected: the Kubernetes API requires authentication.
|
||||
|
||||
---
|
||||
|
||||
## Listing running containers
|
||||
|
||||
- Containers are manipulated through *pods*
|
||||
@@ -467,3 +464,117 @@ class: extra-details
|
||||
|
||||
[KEP-0009]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/0009-node-heartbeat.md
|
||||
[node controller documentation]: https://kubernetes.io/docs/concepts/architecture/nodes/#node-controller
|
||||
|
||||
---
|
||||
|
||||
## Services
|
||||
|
||||
- A *service* is a stable endpoint to connect to "something"
|
||||
|
||||
(In the initial proposal, they were called "portals")
|
||||
|
||||
.exercise[
|
||||
|
||||
- List the services on our cluster with one of these commands:
|
||||
```bash
|
||||
kubectl get services
|
||||
kubectl get svc
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
There is already one service on our cluster: the Kubernetes API itself.
|
||||
|
||||
---
|
||||
|
||||
## ClusterIP services
|
||||
|
||||
- A `ClusterIP` service is internal, available from the cluster only
|
||||
|
||||
- This is useful for introspection from within containers
|
||||
|
||||
.exercise[
|
||||
|
||||
- Try to connect to the API:
|
||||
```bash
|
||||
curl -k https://`10.96.0.1`
|
||||
```
|
||||
|
||||
- `-k` is used to skip certificate verification
|
||||
|
||||
- Make sure to replace 10.96.0.1 with the CLUSTER-IP shown by `kubectl get svc`
|
||||
|
||||
]
|
||||
|
||||
The command above should either time out, or show an authentication error. Why?
|
||||
|
||||
---
|
||||
|
||||
## Time out
|
||||
|
||||
- Connections to ClusterIP services only work *from within the cluster*
|
||||
|
||||
- If we are outside the cluster, the `curl` command will probably time out
|
||||
|
||||
(Because the IP address, e.g. 10.96.0.1, isn't routed properly outside the cluster)
|
||||
|
||||
- This is the case with most "real" Kubernetes clusters
|
||||
|
||||
- To try the connection from within the cluster, we can use [shpod](https://github.com/jpetazzo/shpod)
|
||||
|
||||
---
|
||||
|
||||
## Authentication error
|
||||
|
||||
This is what we should see when connecting from within the cluster:
|
||||
```json
|
||||
$ curl -k https://10.96.0.1
|
||||
{
|
||||
"kind": "Status",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
|
||||
},
|
||||
"status": "Failure",
|
||||
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
|
||||
"reason": "Forbidden",
|
||||
"details": {
|
||||
|
||||
},
|
||||
"code": 403
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Explanations
|
||||
|
||||
- We can see `kind`, `apiVersion`, `metadata`
|
||||
|
||||
- These are typical of a Kubernetes API reply
|
||||
|
||||
- Because we *are* talking to the Kubernetes API
|
||||
|
||||
- The Kubernetes API tells us "Forbidden"
|
||||
|
||||
(because it requires authentication)
|
||||
|
||||
- The Kubernetes API is reachable from within the cluster
|
||||
|
||||
(many apps integrating with Kubernetes will use this)
|
||||
|
||||
---
|
||||
|
||||
## DNS integration
|
||||
|
||||
- Each service also gets a DNS record
|
||||
|
||||
- The Kubernetes DNS resolver is available *from within pods*
|
||||
|
||||
(and sometimes, from within nodes, depending on configuration)
|
||||
|
||||
- Code running in pods can connect to services using their name
|
||||
|
||||
(e.g. https://kubernetes/...)
|
||||
|
||||
@@ -101,7 +101,7 @@ If we wanted to talk to the API, we would need to:
|
||||
|
||||
<!--
|
||||
```wait /version```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
- Terminate the proxy:
|
||||
|
||||
@@ -20,10 +20,9 @@
|
||||
|
||||
.exercise[
|
||||
|
||||
- Let's ping `1.1.1.1`, Cloudflare's
|
||||
[public DNS resolver](https://blog.cloudflare.com/announcing-1111/):
|
||||
- Let's ping the address of `localhost`, the loopback interface:
|
||||
```bash
|
||||
kubectl run pingpong --image alpine ping 1.1.1.1
|
||||
kubectl run pingpong --image alpine ping 127.0.0.1
|
||||
```
|
||||
|
||||
<!-- ```hide kubectl wait deploy/pingpong --for condition=available``` -->
|
||||
@@ -153,9 +152,11 @@ pod/pingpong-7c8bbcd9bc-6c9qz 1/1 Running 0 10m
|
||||
kubectl logs deploy/pingpong --tail 1 --follow
|
||||
```
|
||||
|
||||
- Leave that command running, so that we can keep an eye on these logs
|
||||
|
||||
<!--
|
||||
```wait seq=3```
|
||||
```keys ^C```
|
||||
```tmux split-pane -h```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -186,6 +187,54 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
---
|
||||
|
||||
## Log streaming
|
||||
|
||||
- Let's look again at the output of `kubectl logs`
|
||||
|
||||
(the one we started before scaling up)
|
||||
|
||||
- `kubectl logs` shows us one line per second
|
||||
|
||||
- We could expect 3 lines per second
|
||||
|
||||
(since we should now have 3 pods running `ping`)
|
||||
|
||||
- Let's try to figure out what's happening!
|
||||
|
||||
---
|
||||
|
||||
## Streaming logs of multiple pods
|
||||
|
||||
- What happens if we restart `kubectl logs`?
|
||||
|
||||
.exercise[
|
||||
|
||||
- Interrupt `kubectl logs` (with Ctrl-C)
|
||||
|
||||
<!--
|
||||
```tmux last-pane```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- Restart it:
|
||||
```bash
|
||||
kubectl logs deploy/pingpong --tail 1 --follow
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait using pod/pingpong-```
|
||||
```tmux last-pane```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
`kubectl logs` will warn us that multiple pods were found, and that it's showing us only one of them.
|
||||
|
||||
Let's leave `kubectl logs` running while we keep exploring.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Resilience
|
||||
|
||||
- The *deployment* `pingpong` watches its *replica set*
|
||||
@@ -196,27 +245,56 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
.exercise[
|
||||
|
||||
- In a separate window, list pods, and keep watching them:
|
||||
- In a separate window, watch the list of pods:
|
||||
```bash
|
||||
kubectl get pods -w
|
||||
watch kubectl get pods
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait Running```
|
||||
```keys ^C```
|
||||
```hide kubectl wait deploy pingpong --for condition=available```
|
||||
```keys kubectl delete pod ping```
|
||||
```copypaste pong-..........-.....```
|
||||
```wait Every 2.0s```
|
||||
```tmux split-pane -v```
|
||||
-->
|
||||
|
||||
- Destroy a pod:
|
||||
- Destroy the pod currently shown by `kubectl logs`:
|
||||
```
|
||||
kubectl delete pod pingpong-xxxxxxxxxx-yyyyy
|
||||
```
|
||||
|
||||
<!--
|
||||
```tmux select-pane -t 0```
|
||||
```copy pingpong-[^-]*-.....```
|
||||
```tmux last-pane```
|
||||
```keys kubectl delete pod ```
|
||||
```paste```
|
||||
```key ^J```
|
||||
```check```
|
||||
```key ^D```
|
||||
```tmux select-pane -t 1```
|
||||
```key ^C```
|
||||
```key ^D```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## What happened?
|
||||
|
||||
- `kubectl delete pod` terminates the pod gracefully
|
||||
|
||||
(sending it the TERM signal and waiting for it to shutdown)
|
||||
|
||||
- As soon as the pod is in "Terminating" state, the Replica Set replaces it
|
||||
|
||||
- But we can still see the output of the "Terminating" pod in `kubectl logs`
|
||||
|
||||
- Until 30 seconds later, when the grace period expires
|
||||
|
||||
- The pod is then killed, and `kubectl logs` exits
|
||||
|
||||
---
|
||||
|
||||
|
||||
## What if we wanted something different?
|
||||
|
||||
- What if we wanted to start a "one-shot" container that *doesn't* get restarted?
|
||||
@@ -234,6 +312,73 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
---
|
||||
|
||||
## Scheduling periodic background work
|
||||
|
||||
- A Cron Job is a job that will be executed at specific intervals
|
||||
|
||||
(the name comes from the traditional cronjobs executed by the UNIX crond)
|
||||
|
||||
- It requires a *schedule*, represented as five space-separated fields:
|
||||
|
||||
- minute [0,59]
|
||||
- hour [0,23]
|
||||
- day of the month [1,31]
|
||||
- month of the year [1,12]
|
||||
- day of the week ([0,6] with 0=Sunday)
|
||||
|
||||
- `*` means "all valid values"; `/N` means "every N"
|
||||
|
||||
- Example: `*/3 * * * *` means "every three minutes"
|
||||
|
||||
---
|
||||
|
||||
## Creating a Cron Job
|
||||
|
||||
- Let's create a simple job to be executed every three minutes
|
||||
|
||||
- Cron Jobs need to terminate, otherwise they'd run forever
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the Cron Job:
|
||||
```bash
|
||||
kubectl run every3mins --schedule="*/3 * * * *" --restart=OnFailure \
|
||||
--image=alpine sleep 10
|
||||
```
|
||||
|
||||
- Check the resource that was created:
|
||||
```bash
|
||||
kubectl get cronjobs
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Cron Jobs in action
|
||||
|
||||
- At the specified schedule, the Cron Job will create a Job
|
||||
|
||||
- The Job will create a Pod
|
||||
|
||||
- The Job will make sure that the Pod completes
|
||||
|
||||
(re-creating another one if it fails, for instance if its node fails)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the Jobs that are created:
|
||||
```bash
|
||||
kubectl get jobs
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(It will take a few minutes before the first job is scheduled.)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## What about that deprecation warning?
|
||||
|
||||
- As we can see from the previous slide, `kubectl run` can do many things
|
||||
@@ -257,12 +402,12 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
## Various ways of creating resources
|
||||
|
||||
- `kubectl run`
|
||||
- `kubectl run`
|
||||
|
||||
- easy way to get started
|
||||
- versatile
|
||||
|
||||
- `kubectl create <resource>`
|
||||
- `kubectl create <resource>`
|
||||
|
||||
- explicit, but lacks some features
|
||||
- can't create a CronJob before Kubernetes 1.14
|
||||
@@ -309,7 +454,7 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
<!--
|
||||
```wait seq=```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -338,6 +483,8 @@ class: extra-details
|
||||
kubectl logs -l run=pingpong --tail 1 -f
|
||||
```
|
||||
|
||||
<!-- ```wait error:``` -->
|
||||
|
||||
]
|
||||
|
||||
We see a message like the following one:
|
||||
@@ -406,15 +553,36 @@ class: extra-details
|
||||
|
||||
---
|
||||
|
||||
## Aren't we flooding 1.1.1.1?
|
||||
class: extra-details
|
||||
|
||||
- If you're wondering this, good question!
|
||||
## Party tricks involving IP addresses
|
||||
|
||||
- Don't worry, though:
|
||||
- It is possible to specify an IP address with less than 4 bytes
|
||||
|
||||
*APNIC's research group held the IP addresses 1.1.1.1 and 1.0.0.1. While the addresses were valid, so many people had entered them into various random systems that they were continuously overwhelmed by a flood of garbage traffic. APNIC wanted to study this garbage traffic but any time they'd tried to announce the IPs, the flood would overwhelm any conventional network.*
|
||||
(example: `127.1`)
|
||||
|
||||
(Source: https://blog.cloudflare.com/announcing-1111/)
|
||||
- Zeroes are then inserted in the middle
|
||||
|
||||
- It's very unlikely that our concerted pings manage to produce
|
||||
even a modest blip at Cloudflare's NOC!
|
||||
- As a result, `127.1` expands to `127.0.0.1`
|
||||
|
||||
- So we can `ping 127.1` to ping `localhost`!
|
||||
|
||||
(See [this blog post](https://ma.ttias.be/theres-more-than-one-way-to-write-an-ip-address/
|
||||
) for more details.)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## More party tricks with IP addresses
|
||||
|
||||
- We can also ping `1.1`
|
||||
|
||||
- `1.1` will expand to `1.0.0.1`
|
||||
|
||||
- This is one of the addresses of Cloudflare's
|
||||
[public DNS resolver](https://blog.cloudflare.com/announcing-1111/)
|
||||
|
||||
- This is a quick way to check connectivity
|
||||
|
||||
(if we can reach 1.1, we probably have internet access)
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
<!--
|
||||
```wait RESTARTS```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
```wait AVAILABLE```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- Now, create more `worker` replicas:
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
ship init https://github.com/jpetazzo/kubercoins
|
||||
```
|
||||
|
||||
<!-- ```wait Open browser``` -->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
@@ -189,6 +191,11 @@
|
||||
kubectl logs deploy/worker --tail=10 --follow --namespace=kustomcoins
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait units of work done```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
Note: it might take a minute or two for the worker to start.
|
||||
|
||||
@@ -56,28 +56,6 @@
|
||||
|
||||
---
|
||||
|
||||
## Work in a separate namespace
|
||||
|
||||
- To avoid conflicts with existing resources, let's create and use a new namespace
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create a new namespace:
|
||||
```bash
|
||||
kubectl create namespace orange
|
||||
```
|
||||
|
||||
- Switch to that namespace:
|
||||
```bash
|
||||
kns orange
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
.warning[Make sure to call that namespace `orange`: it is hardcoded in the YAML files.]
|
||||
|
||||
---
|
||||
|
||||
## Deploying Consul
|
||||
|
||||
- We will use a slightly different YAML file
|
||||
@@ -88,7 +66,9 @@
|
||||
|
||||
- the corresponding `volumeMounts` in the Pod spec
|
||||
|
||||
- the namespace `orange` used for discovery of Pods
|
||||
- the label `consul` has been changed to `persistentconsul`
|
||||
<br/>
|
||||
(to avoid conflicts with the other Stateful Set)
|
||||
|
||||
.exercise[
|
||||
|
||||
@@ -117,7 +97,7 @@
|
||||
kubectl get pv
|
||||
```
|
||||
|
||||
- The Pod `consul-0` is not scheduled yet:
|
||||
- The Pod `persistentconsul-0` is not scheduled yet:
|
||||
```bash
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
@@ -132,9 +112,9 @@
|
||||
|
||||
- In a Stateful Set, the Pods are started one by one
|
||||
|
||||
- `consul-1` won't be created until `consul-0` is running
|
||||
- `persistentconsul-1` won't be created until `persistentconsul-0` is running
|
||||
|
||||
- `consul-0` has a dependency on an unbound Persistent Volume Claim
|
||||
- `persistentconsul-0` has a dependency on an unbound Persistent Volume Claim
|
||||
|
||||
- The scheduler won't schedule the Pod until the PVC is bound
|
||||
|
||||
@@ -172,7 +152,7 @@
|
||||
|
||||
- Once a PVC is bound, its pod can start normally
|
||||
|
||||
- Once the pod `consul-0` has started, `consul-1` can be created, etc.
|
||||
- Once the pod `persistentconsul-0` has started, `persistentconsul-1` can be created, etc.
|
||||
|
||||
- Eventually, our Consul cluster is up, and backend by "persistent" volumes
|
||||
|
||||
@@ -180,7 +160,7 @@
|
||||
|
||||
- Check that our Consul clusters has 3 members indeed:
|
||||
```bash
|
||||
kubectl exec consul-0 consul members
|
||||
kubectl exec persistentconsul-0 consul members
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
|
||||
- Download the `kubectl` binary from one of these links:
|
||||
|
||||
[Linux](https://storage.googleapis.com/kubernetes-release/release/v1.15.4/bin/linux/amd64/kubectl)
|
||||
[Linux](https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl)
|
||||
|
|
||||
[macOS](https://storage.googleapis.com/kubernetes-release/release/v1.15.4/bin/darwin/amd64/kubectl)
|
||||
[macOS](https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/darwin/amd64/kubectl)
|
||||
|
|
||||
[Windows](https://storage.googleapis.com/kubernetes-release/release/v1.15.4/bin/windows/amd64/kubectl.exe)
|
||||
[Windows](https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/windows/amd64/kubectl.exe)
|
||||
|
||||
- On Linux and macOS, make the binary executable with `chmod +x kubectl`
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ Exactly what we need!
|
||||
sudo chmod +x /usr/local/bin/stern
|
||||
```
|
||||
|
||||
- On OS X, just `brew install stern`
|
||||
|
||||
<!-- ##VERSION## -->
|
||||
|
||||
---
|
||||
@@ -82,14 +84,14 @@ Exactly what we need!
|
||||
|
||||
.exercise[
|
||||
|
||||
- View the logs for all the rng containers:
|
||||
- View the logs for all the pingpong containers:
|
||||
```bash
|
||||
stern rng
|
||||
stern pingpong
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait HTTP/1.1```
|
||||
```keys ^C```
|
||||
```wait seq=```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -115,7 +117,7 @@ Exactly what we need!
|
||||
|
||||
<!--
|
||||
```wait weave-npc```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -136,14 +138,14 @@ Exactly what we need!
|
||||
|
||||
.exercise[
|
||||
|
||||
- View the logs for all the things started with `kubectl create deployment`:
|
||||
- View the logs for all the things started with `kubectl run`:
|
||||
```bash
|
||||
stern -l app
|
||||
stern -l run
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait units of work```
|
||||
```keys ^C```
|
||||
```wait seq=```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -218,6 +218,18 @@ class: extra-details
|
||||
|
||||
## What's going on?
|
||||
|
||||
- Without the `--network-plugin` flag, kubelet defaults to "no-op" networking
|
||||
|
||||
- It lets the container engine use a default network
|
||||
|
||||
(in that case, we end up with the default Docker bridge)
|
||||
|
||||
- Our pods are running on independent, disconnected, host-local networks
|
||||
|
||||
---
|
||||
|
||||
## What do we need to do?
|
||||
|
||||
- On a normal cluster, kubelet is configured to set up pod networking with CNI plugins
|
||||
|
||||
- This requires:
|
||||
@@ -228,14 +240,6 @@ class: extra-details
|
||||
|
||||
- running kubelet with `--network-plugin=cni`
|
||||
|
||||
- Without the `--network-plugin` flag, kubelet defaults to "no-op" networking
|
||||
|
||||
- It lets the container engine use a default network
|
||||
|
||||
(in that case, we end up with the default Docker bridge)
|
||||
|
||||
- Our pods are running on independent, disconnected, host-local networks
|
||||
|
||||
---
|
||||
|
||||
## Using network plugins
|
||||
@@ -394,7 +398,7 @@ class: extra-details
|
||||
|
||||
- Start kube-proxy:
|
||||
```bash
|
||||
sudo kube-proxy --kubeconfig ~/kubeconfig
|
||||
sudo kube-proxy --kubeconfig ~/.kube/config
|
||||
```
|
||||
|
||||
- Expose our Deployment:
|
||||
|
||||
@@ -120,6 +120,12 @@ This is our game plan:
|
||||
kubectl create deployment testweb --image=nginx
|
||||
```
|
||||
|
||||
<!--
|
||||
```bash
|
||||
kubectl wait deployment testweb --for condition=available
|
||||
```
|
||||
-->
|
||||
|
||||
- Find out the IP address of the pod with one of these two commands:
|
||||
```bash
|
||||
kubectl get pods -o wide -l app=testweb
|
||||
@@ -154,6 +160,11 @@ The `curl` command should show us the "Welcome to nginx!" page.
|
||||
curl $IP
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait curl```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
The `curl` command should now time out.
|
||||
|
||||
@@ -121,7 +121,7 @@ Examples:
|
||||
|
||||
## One operator in action
|
||||
|
||||
- We will install the UPMC Enterprises ElasticSearch operator
|
||||
- We will install [Elastic Cloud on Kubernetes](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html), an ElasticSearch operator
|
||||
|
||||
- This operator requires PersistentVolumes
|
||||
|
||||
@@ -206,51 +206,92 @@ Now, the StorageClass should have `(default)` next to its name.
|
||||
|
||||
## Install the ElasticSearch operator
|
||||
|
||||
- The operator needs:
|
||||
- The operator provides:
|
||||
|
||||
- a Deployment for its controller
|
||||
- a few CustomResourceDefinitions
|
||||
- a Namespace for its other resources
|
||||
- a ValidatingWebhookConfiguration for type checking
|
||||
- a StatefulSet for its controller and webhook code
|
||||
- a ServiceAccount, ClusterRole, ClusterRoleBinding for permissions
|
||||
- a Namespace
|
||||
|
||||
- We have grouped all the definitions for these resources in a YAML file
|
||||
- All these resources are grouped in a convenient YAML file
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install the operator:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/elasticsearch-operator.yaml
|
||||
kubectl apply -f ~/container.training/k8s/eck-operator.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Wait for the operator to be ready
|
||||
## Check our new custom resources
|
||||
|
||||
- Some operators require to create their CRDs separately
|
||||
|
||||
- This operator will create its CRD itself
|
||||
|
||||
(i.e. the CRD is not listed in the YAML that we applied earlier)
|
||||
- Let's see which CRDs were created
|
||||
|
||||
.exercise[
|
||||
|
||||
- Wait until the `elasticsearchclusters` CRD shows up:
|
||||
- List all CRDs:
|
||||
```bash
|
||||
kubectl get crds
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
This operator supports ElasticSearch, but also Kibana and APM. Cool!
|
||||
|
||||
---
|
||||
|
||||
## Create the `eck-demo` namespace
|
||||
|
||||
- For clarity, we will create everything in a new namespace, `eck-demo`
|
||||
|
||||
- This namespace is hard-coded in the YAML files that we are going to use
|
||||
|
||||
- We need to create that namespace
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the `eck-demo` namespace:
|
||||
```bash
|
||||
kubectl create namespace eck-demo
|
||||
```
|
||||
|
||||
- Switch to that namespace:
|
||||
```bash
|
||||
kns eck-demo
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Can we use a different namespace?
|
||||
|
||||
Yes, but then we need to update all the YAML manifests that we
|
||||
are going to apply in the next slides.
|
||||
|
||||
The `eck-demo` namespace is hard-coded in these YAML manifests.
|
||||
|
||||
Why?
|
||||
|
||||
Because when defining a ClusterRoleBinding that references a
|
||||
ServiceAccount, we have to indicate in which namespace the
|
||||
ServiceAccount is located.
|
||||
|
||||
---
|
||||
|
||||
## Create an ElasticSearch resource
|
||||
|
||||
- We can now create a resource with `kind: ElasticsearchCluster`
|
||||
- We can now create a resource with `kind: ElasticSearch`
|
||||
|
||||
- The YAML for that resource will specify all the desired parameters:
|
||||
|
||||
- how many nodes do we want of each type (client, master, data)
|
||||
- how many nodes we want
|
||||
- image to use
|
||||
- add-ons (kibana, cerebro, ...)
|
||||
- whether to use TLS or not
|
||||
@@ -260,7 +301,7 @@ Now, the StorageClass should have `(default)` next to its name.
|
||||
|
||||
- Create our ElasticSearch cluster:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/elasticsearch-cluster.yaml
|
||||
kubectl apply -f ~/container.training/k8s/eck-elasticsearch.yaml
|
||||
```
|
||||
|
||||
]
|
||||
@@ -269,49 +310,88 @@ Now, the StorageClass should have `(default)` next to its name.
|
||||
|
||||
## Operator in action
|
||||
|
||||
- Over the next minutes, the operator will create:
|
||||
- Over the next minutes, the operator will create our ES cluster
|
||||
|
||||
- StatefulSets (one for master nodes, one for data nodes)
|
||||
|
||||
- Deployments (for client nodes; and for add-ons like cerebro and kibana)
|
||||
|
||||
- Services (for all these pods)
|
||||
- It will report our cluster status through the CRD
|
||||
|
||||
.exercise[
|
||||
|
||||
- Wait for all the StatefulSets to be fully up and running:
|
||||
- Check the logs of the operator:
|
||||
```bash
|
||||
kubectl get statefulsets -w
|
||||
stern --namespace=elastic-system operator
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait elastic-operator-0```
|
||||
```tmux split-pane -v```
|
||||
--->
|
||||
|
||||
- Watch the status of the cluster through the CRD:
|
||||
```bash
|
||||
kubectl get es -w
|
||||
```
|
||||
|
||||
<!--
|
||||
```longwait green```
|
||||
```key ^C```
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Connecting to our cluster
|
||||
|
||||
- Since connecting directly to the ElasticSearch API is a bit raw,
|
||||
<br/>we'll connect to the cerebro frontend instead
|
||||
- It's not easy to use the ElasticSearch API from the shell
|
||||
|
||||
- But let's check at least if ElasticSearch is up!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit the cerebro service to change its type from ClusterIP to NodePort:
|
||||
- Get the ClusterIP of our ES instance:
|
||||
```bash
|
||||
kubectl patch svc cerebro-es -p "spec: { type: NodePort }"
|
||||
kubectl get services
|
||||
```
|
||||
|
||||
- Retrieve the NodePort that was allocated:
|
||||
- Issue a request with `curl`:
|
||||
```bash
|
||||
kubectl get svc cerebro-es
|
||||
curl http://`CLUSTERIP`:9200
|
||||
```
|
||||
|
||||
- Connect to that port with a browser
|
||||
|
||||
]
|
||||
|
||||
We get an authentication error. Our cluster is protected!
|
||||
|
||||
---
|
||||
|
||||
## (Bonus) Setup filebeat
|
||||
## Obtaining the credentials
|
||||
|
||||
- The operator creates a user named `elastic`
|
||||
|
||||
- It generates a random password and stores it in a Secret
|
||||
|
||||
.exercise[
|
||||
|
||||
- Extract the password:
|
||||
```bash
|
||||
kubectl get secret demo-es-elastic-user \
|
||||
-o go-template="{{ .data.elastic | base64decode }} "
|
||||
```
|
||||
|
||||
- Use it to connect to the API:
|
||||
```bash
|
||||
curl -u elastic:`PASSWORD` http://`CLUSTERIP`:9200
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should see a JSON payload with the `"You Know, for Search"` tagline.
|
||||
|
||||
---
|
||||
|
||||
## Sending data to the cluster
|
||||
|
||||
- Let's send some data to our brand new ElasticSearch cluster!
|
||||
|
||||
@@ -321,22 +401,170 @@ Now, the StorageClass should have `(default)` next to its name.
|
||||
|
||||
- Deploy filebeat:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/filebeat.yaml
|
||||
kubectl apply -f ~/container.training/k8s/eck-filebeat.yaml
|
||||
```
|
||||
|
||||
- Wait until some pods are up:
|
||||
```bash
|
||||
watch kubectl get pods -l k8s-app=filebeat
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait Running```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- Check that a filebeat index was created:
|
||||
```bash
|
||||
curl -u elastic:`PASSWORD` http://`CLUSTERIP`:9200/_cat/indices
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should see at least one index being created in cerebro.
|
||||
---
|
||||
|
||||
## Deploying an instance of Kibana
|
||||
|
||||
- Kibana can visualize the logs injected by filebeat
|
||||
|
||||
- The ECK operator can also manage Kibana
|
||||
|
||||
- Let's give it a try!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Deploy a Kibana instance:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/eck-kibana.yaml
|
||||
```
|
||||
|
||||
- Wait for it to be ready:
|
||||
```bash
|
||||
kubectl get kibana -w
|
||||
```
|
||||
|
||||
<!--
|
||||
```longwait green```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## (Bonus) Access log data with kibana
|
||||
## Connecting to Kibana
|
||||
|
||||
- Let's expose kibana (by making kibana-es a NodePort too)
|
||||
- Kibana is automatically set up to conect to ElasticSearch
|
||||
|
||||
- Then access kibana
|
||||
(this is arranged by the YAML that we're using)
|
||||
|
||||
- We'll need to configure kibana indexes
|
||||
- However, it will ask for authentication
|
||||
|
||||
- It's using the same user/password as ElasticSearch
|
||||
|
||||
.exercise[
|
||||
|
||||
- Get the NodePort allocated to Kibana:
|
||||
```bash
|
||||
kubectl get services
|
||||
```
|
||||
|
||||
- Connect to it with a web browser
|
||||
|
||||
- Use the same user/password as before
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Setting up Kibana
|
||||
|
||||
After the Kibana UI loads, we need to click around a bit
|
||||
|
||||
.exercise[
|
||||
|
||||
- Pick "explore on my own"
|
||||
|
||||
- Click on Use Elasticsearch data / Connect to your Elasticsearch index"
|
||||
|
||||
- Enter `filebeat-*` for the index pattern and click "Next step"
|
||||
|
||||
- Select `@timestamp` as time filter field name
|
||||
|
||||
- Click on "discover" (the small icon looking like a compass on the left bar)
|
||||
|
||||
- Play around!
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Scaling up the cluster
|
||||
|
||||
- At this point, we have only one node
|
||||
|
||||
- We are going to scale up
|
||||
|
||||
- But first, we'll deploy Cerebro, an UI for ElasticSearch
|
||||
|
||||
- This will let us see the state of the cluster, how indexes are sharded, etc.
|
||||
|
||||
---
|
||||
|
||||
## Deploying Cerebro
|
||||
|
||||
- Cerebro is stateless, so it's fairly easy to deploy
|
||||
|
||||
(one Deployment + one Service)
|
||||
|
||||
- However, it needs the address and credentials for ElasticSearch
|
||||
|
||||
- We prepared yet another manifest for that!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Deploy Cerebro:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/eck-cerebro.yaml
|
||||
```
|
||||
|
||||
- Lookup the NodePort number and connect to it:
|
||||
```bash
|
||||
kuebctl get services
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Scaling up the cluster
|
||||
|
||||
- We can see on Cerebro that the cluster is "yellow"
|
||||
|
||||
(because our index is not replicated)
|
||||
|
||||
- Let's change that!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit the ElasticSearch cluster manifest:
|
||||
```bash
|
||||
kubectl edit es demo
|
||||
```
|
||||
|
||||
- Find the field `count: 1` and change it to 3
|
||||
|
||||
- Save and quit
|
||||
|
||||
<!--
|
||||
```wait Please edit```
|
||||
```keys /count:```
|
||||
```key ^J```
|
||||
```keys $r3:x```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
@@ -376,13 +604,11 @@ We should see at least one index being created in cerebro.
|
||||
|
||||
- Look at the ElasticSearch resource definition
|
||||
|
||||
(`~/container.training/k8s/elasticsearch-cluster.yaml`)
|
||||
(`~/container.training/k8s/eck-elasticsearch.yaml`)
|
||||
|
||||
- What should happen if we flip the `use-tls` flag? Twice?
|
||||
- What should happen if we flip the TLS flag? Twice?
|
||||
|
||||
- What should happen if we remove / re-add the kibana or cerebro sections?
|
||||
|
||||
- What should happen if we change the number of nodes?
|
||||
- What should happen if we add another group of nodes?
|
||||
|
||||
- What if we want different images or parameters for the different nodes?
|
||||
|
||||
|
||||
@@ -11,16 +11,36 @@
|
||||
|
||||
- Deploy everything else:
|
||||
```bash
|
||||
set -u
|
||||
for SERVICE in hasher rng webui worker; do
|
||||
kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
|
||||
done
|
||||
kubectl create deployment hasher --image=dockercoins/hasher:v0.1
|
||||
kubectl create deployment rng --image=dockercoins/rng:v0.1
|
||||
kubectl create deployment webui --image=dockercoins/webui:v0.1
|
||||
kubectl create deployment worker --image=dockercoins/worker:v0.1
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Deploying other images
|
||||
|
||||
- If we wanted to deploy images from another registry ...
|
||||
|
||||
- ... Or with a different tag ...
|
||||
|
||||
- ... We could use the following snippet:
|
||||
|
||||
```bash
|
||||
REGISTRY=dockercoins
|
||||
TAG=v0.1
|
||||
for SERVICE in hasher rng webui worker; do
|
||||
kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Is this working?
|
||||
|
||||
- After waiting for the deployment to complete, let's look at the logs!
|
||||
@@ -88,7 +108,7 @@ kubectl wait deploy/worker --for condition=available
|
||||
|
||||
<!--
|
||||
```wait units of work done, updating hash counter```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -220,6 +220,8 @@
|
||||
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
```
|
||||
|
||||
<!-- ```wait apiVersion``` -->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
@@ -240,6 +242,16 @@
|
||||
|
||||
- Save, quit
|
||||
|
||||
<!--
|
||||
```keys /--enable-admission-plugins=```
|
||||
```key ^J```
|
||||
```key $```
|
||||
```keys a,PodSecurityPolicy```
|
||||
```key Escape```
|
||||
```keys :wq```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
@@ -271,6 +283,8 @@
|
||||
kubectl run testpsp1 --image=nginx --restart=Never
|
||||
```
|
||||
|
||||
<!-- ```wait forbidden: no providers available``` -->
|
||||
|
||||
- Try to create a Deployment:
|
||||
```bash
|
||||
kubectl run testpsp2 --image=nginx
|
||||
@@ -498,3 +512,22 @@ class: extra-details
|
||||
- bind `psp:restricted` to the group `system:authenticated`
|
||||
|
||||
- bind `psp:privileged` to the ServiceAccount `kube-system:default`
|
||||
|
||||
---
|
||||
|
||||
## Fixing the cluster
|
||||
|
||||
- Let's disable the PSP admission plugin
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit the Kubernetes API server static pod manifest
|
||||
|
||||
- Remove the PSP admission plugin
|
||||
|
||||
- This can be done with this one-liner:
|
||||
```bash
|
||||
sudo sed -i s/,PodSecurityPolicy// /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
@@ -197,7 +197,7 @@ If you want to use an external key/value store, add one of the following:
|
||||
|
||||
<!--
|
||||
```longwait PX node status reports portworx service is healthy```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -240,6 +240,25 @@ If you want to use an external key/value store, add one of the following:
|
||||
|
||||
---
|
||||
|
||||
## Check our default Storage Class
|
||||
|
||||
- The YAML manifest applied earlier should define a default storage class
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check that we have a default storage class:
|
||||
```bash
|
||||
kubectl get storageclass
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
There should be a storage class showing as `portworx-replicated (default)`.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Our default Storage Class
|
||||
|
||||
This is our Storage Class (in `k8s/storage-class.yaml`):
|
||||
@@ -265,28 +284,6 @@ parameters:
|
||||
|
||||
---
|
||||
|
||||
## Creating our Storage Class
|
||||
|
||||
- Let's apply that YAML file!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the Storage Class:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/storage-class.yaml
|
||||
```
|
||||
|
||||
- Check that it is now available:
|
||||
```bash
|
||||
kubectl get sc
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
It should show as `portworx-replicated (default)`.
|
||||
|
||||
---
|
||||
|
||||
## Our Postgres Stateful set
|
||||
|
||||
- The next slide shows `k8s/postgres.yaml`
|
||||
@@ -326,7 +323,7 @@ spec:
|
||||
schedulerName: stork
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:10.5
|
||||
image: postgres:11
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql/data
|
||||
name: postgres
|
||||
@@ -377,7 +374,7 @@ spec:
|
||||
autopilot prompt detection expects $ or # at the beginning of the line.
|
||||
```wait postgres@postgres```
|
||||
```keys PS1="\u@\h:\w\n\$ "```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
- Check that default databases have been created correctly:
|
||||
@@ -431,7 +428,7 @@ autopilot prompt detection expects $ or # at the beginning of the line.
|
||||
psql demo -c "select count(*) from pgbench_accounts"
|
||||
```
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
<!-- ```key ^D``` -->
|
||||
|
||||
]
|
||||
|
||||
@@ -494,7 +491,7 @@ By "disrupt" we mean: "disconnect it from the network".
|
||||
|
||||
- Logout to go back on `node1`
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
<!-- ```key ^D``` -->
|
||||
|
||||
- Watch the events unfolding with `kubectl get events -w` and `kubectl get pods -w`
|
||||
|
||||
@@ -522,7 +519,7 @@ By "disrupt" we mean: "disconnect it from the network".
|
||||
<!--
|
||||
```wait postgres@postgres```
|
||||
```keys PS1="\u@\h:\w\n\$ "```
|
||||
```keys ^J```
|
||||
```key ^J```
|
||||
-->
|
||||
|
||||
- Check the number of rows in the `pgbench_accounts` table:
|
||||
@@ -530,7 +527,7 @@ By "disrupt" we mean: "disconnect it from the network".
|
||||
psql demo -c "select count(*) from pgbench_accounts"
|
||||
```
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
<!-- ```key ^D``` -->
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -60,10 +60,12 @@
|
||||
|
||||
(by default: every minute; can be more/less frequent)
|
||||
|
||||
- If you're worried about parsing overhead: exporters can also use protobuf
|
||||
|
||||
- The list of URLs to scrape (the *scrape targets*) is defined in configuration
|
||||
|
||||
.footnote[Worried about the overhead of parsing a text format?
|
||||
<br/>
|
||||
Check this [comparison](https://github.com/RichiH/OpenMetrics/blob/master/markdown/protobuf_vs_text.md) of the text format with the (now deprecated) protobuf format!]
|
||||
|
||||
---
|
||||
|
||||
## Defining scrape targets
|
||||
@@ -202,32 +204,46 @@ We need to:
|
||||
|
||||
## Step 1: install Helm
|
||||
|
||||
- If we already installed Helm earlier, these commands won't break anything
|
||||
- If we already installed Helm earlier, this command won't break anything
|
||||
|
||||
.exercice[
|
||||
.exercise[
|
||||
|
||||
- Install Tiller (Helm's server-side component) on our cluster:
|
||||
- Install the Helm CLI:
|
||||
```bash
|
||||
helm init
|
||||
```
|
||||
|
||||
- Give Tiller permission to deploy things on our cluster:
|
||||
```bash
|
||||
kubectl create clusterrolebinding add-on-cluster-admin \
|
||||
--clusterrole=cluster-admin --serviceaccount=kube-system:default
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get-helm-3 \
|
||||
| bash
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Step 2: install Prometheus
|
||||
## Step 2: add the `stable` repo
|
||||
|
||||
- Skip this if we already installed Prometheus earlier
|
||||
- This will add the repository containing the chart for Prometheus
|
||||
|
||||
(in doubt, check with `helm list`)
|
||||
- This command is idempotent
|
||||
|
||||
.exercice[
|
||||
(it won't break anything if the repository was already added)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Add the repository:
|
||||
```bash
|
||||
helm repo add stable https://kubernetes-charts.storage.googleapis.com/
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Step 3: install Prometheus
|
||||
|
||||
- The following command, just like the previous ones, is idempotent
|
||||
|
||||
(it won't error out if Prometheus is already installed)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install Prometheus on our cluster:
|
||||
```bash
|
||||
|
||||
@@ -14,7 +14,27 @@
|
||||
|
||||
## Rolling updates
|
||||
|
||||
- With rolling updates, when a resource is updated, it happens progressively
|
||||
- With rolling updates, when a Deployment is updated, it happens progressively
|
||||
|
||||
- The Deployment controls multiple Replica Sets
|
||||
|
||||
- Each Replica Set is a group of identical Pods
|
||||
|
||||
(with the same image, arguments, parameters ...)
|
||||
|
||||
- During the rolling update, we have at least two Replica Sets:
|
||||
|
||||
- the "new" set (corresponding to the "target" version)
|
||||
|
||||
- at least one "old" set
|
||||
|
||||
- We can have multiple "old" sets
|
||||
|
||||
(if we start another update before the first one is done)
|
||||
|
||||
---
|
||||
|
||||
## Update strategy
|
||||
|
||||
- Two parameters determine the pace of the rollout: `maxUnavailable` and `maxSurge`
|
||||
|
||||
@@ -61,32 +81,6 @@
|
||||
|
||||
---
|
||||
|
||||
## Building a new version of the `worker` service
|
||||
|
||||
.warning[
|
||||
Only run these commands if you have built and pushed DockerCoins to a local registry.
|
||||
<br/>
|
||||
If you are using images from the Docker Hub (`dockercoins/worker:v0.1`), skip this.
|
||||
]
|
||||
|
||||
.exercise[
|
||||
|
||||
- Go to the `stacks` directory (`~/container.training/stacks`)
|
||||
|
||||
- Edit `dockercoins/worker/worker.py`; update the first `sleep` line to sleep 1 second
|
||||
|
||||
- Build a new tag and push it to the registry:
|
||||
```bash
|
||||
#export REGISTRY=localhost:3xxxx
|
||||
export TAG=v0.2
|
||||
docker-compose -f dockercoins.yml build
|
||||
docker-compose -f dockercoins.yml push
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Rolling out the new `worker` service
|
||||
|
||||
.exercise[
|
||||
@@ -100,12 +94,12 @@ If you are using images from the Docker Hub (`dockercoins/worker:v0.1`), skip th
|
||||
|
||||
<!--
|
||||
```wait NAME```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- Update `worker` either with `kubectl edit`, or by running:
|
||||
```bash
|
||||
kubectl set image deploy worker worker=$REGISTRY/worker:$TAG
|
||||
kubectl set image deploy worker worker=dockercoins/worker:v0.2
|
||||
```
|
||||
|
||||
]
|
||||
@@ -146,8 +140,7 @@ That rollout should be pretty quick. What shows in the web UI?
|
||||
|
||||
- Update `worker` by specifying a non-existent image:
|
||||
```bash
|
||||
export TAG=v0.3
|
||||
kubectl set image deploy worker worker=$REGISTRY/worker:$TAG
|
||||
kubectl set image deploy worker worker=dockercoins/worker:v0.3
|
||||
```
|
||||
|
||||
- Check what's going on:
|
||||
@@ -157,7 +150,7 @@ That rollout should be pretty quick. What shows in the web UI?
|
||||
|
||||
<!--
|
||||
```wait Waiting for deployment```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -216,27 +209,14 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check which port the dashboard is on:
|
||||
```bash
|
||||
kubectl -n kube-system get svc socat
|
||||
```
|
||||
- Connect to the dashboard that we deployed earlier
|
||||
|
||||
- Check that we have failures in Deployments, Pods, and Replica Sets
|
||||
|
||||
- Can we see the reason for the failure?
|
||||
|
||||
]
|
||||
|
||||
Note the `3xxxx` port.
|
||||
|
||||
.exercise[
|
||||
|
||||
- Connect to http://oneofournodes:3xxxx/
|
||||
|
||||
<!-- ```open https://node1:3xxxx/``` -->
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
- We have failures in Deployments, Pods, and Replica Sets
|
||||
|
||||
---
|
||||
|
||||
## Recovering from a bad rollout
|
||||
@@ -249,11 +229,7 @@ Note the `3xxxx` port.
|
||||
|
||||
.exercise[
|
||||
|
||||
<!--
|
||||
```keys
|
||||
^C
|
||||
```
|
||||
-->
|
||||
<!-- ```key ^C``` -->
|
||||
|
||||
- Cancel the deployment and wait for the dust to settle:
|
||||
```bash
|
||||
@@ -265,6 +241,137 @@ Note the `3xxxx` port.
|
||||
|
||||
---
|
||||
|
||||
## Rolling back to an older version
|
||||
|
||||
- We reverted to `v0.2`
|
||||
|
||||
- But this version still has a performance problem
|
||||
|
||||
- How can we get back to the previous version?
|
||||
|
||||
---
|
||||
|
||||
## Multiple "undos"
|
||||
|
||||
- What happens if we try `kubectl rollout undo` again?
|
||||
|
||||
.exercise[
|
||||
|
||||
- Try it:
|
||||
```bash
|
||||
kubectl rollout undo deployment worker
|
||||
```
|
||||
|
||||
- Check the web UI, the list of pods ...
|
||||
|
||||
]
|
||||
|
||||
🤔 That didn't work.
|
||||
|
||||
---
|
||||
|
||||
## Multiple "undos" don't work
|
||||
|
||||
- If we see successive versions as a stack:
|
||||
|
||||
- `kubectl rollout undo` doesn't "pop" the last element from the stack
|
||||
|
||||
- it copies the N-1th element to the top
|
||||
|
||||
- Multiple "undos" just swap back and forth between the last two versions!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Go back to v0.2 again:
|
||||
```bash
|
||||
kubectl rollout undo deployment worker
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## In this specific scenario
|
||||
|
||||
- Our version numbers are easy to guess
|
||||
|
||||
- What if we had used git hashes?
|
||||
|
||||
- What if we had changed other parameters in the Pod spec?
|
||||
|
||||
---
|
||||
|
||||
## Listing versions
|
||||
|
||||
- We can list successive versions of a Deployment with `kubectl rollout history`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Look at our successive versions:
|
||||
```bash
|
||||
kubectl rollout history deployment worker
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We don't see *all* revisions.
|
||||
|
||||
We might see something like 1, 4, 5.
|
||||
|
||||
(Depending on how many "undos" we did before.)
|
||||
|
||||
---
|
||||
|
||||
## Explaining deployment revisions
|
||||
|
||||
- These revisions correspond to our Replica Sets
|
||||
|
||||
- This information is stored in the Replica Set annotations
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the annotations for our replica sets:
|
||||
```bash
|
||||
kubectl describe replicasets -l app=worker | grep -A3 ^Annotations
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## What about the missing revisions?
|
||||
|
||||
- The missing revisions are stored in another annotation:
|
||||
|
||||
`deployment.kubernetes.io/revision-history`
|
||||
|
||||
- These are not shown in `kubectl rollout history`
|
||||
|
||||
- We could easily reconstruct the full list with a script
|
||||
|
||||
(if we wanted to!)
|
||||
|
||||
---
|
||||
|
||||
## Rolling back to an older version
|
||||
|
||||
- `kubectl rollout undo` can work with a revision number
|
||||
|
||||
.exercise[
|
||||
|
||||
- Roll back to the "known good" deployment version:
|
||||
```bash
|
||||
kubectl rollout undo deployment worker --to-revision=1
|
||||
```
|
||||
|
||||
- Check the web UI or the list of pods
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Changing rollout parameters
|
||||
@@ -285,7 +392,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: worker
|
||||
image: $REGISTRY/worker:v0.1
|
||||
image: dockercoins/worker:v0.1
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
@@ -316,7 +423,7 @@ class: extra-details
|
||||
spec:
|
||||
containers:
|
||||
- name: worker
|
||||
image: $REGISTRY/worker:v0.1
|
||||
image: dockercoins/worker:v0.1
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
|
||||
@@ -19,17 +19,14 @@
|
||||
|
||||
.exercise[
|
||||
|
||||
- Open two new terminals to check what's going on with pods and deployments:
|
||||
- Open a new terminal to keep an eye on our pods:
|
||||
```bash
|
||||
kubectl get pods -w
|
||||
kubectl get deployments -w
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait RESTARTS```
|
||||
```keys ^C```
|
||||
```wait AVAILABLE```
|
||||
```keys ^C```
|
||||
```tmux split-pane -h```
|
||||
-->
|
||||
|
||||
- Now, create more `worker` replicas:
|
||||
@@ -73,6 +70,11 @@ The graph in the web UI should go up again.
|
||||
kubectl scale deployment worker --replicas=10
|
||||
```
|
||||
|
||||
<!--
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
|
||||
- [minikube](https://kubernetes.io/docs/setup/minikube/),
|
||||
[kubespawn](https://github.com/kinvolk/kube-spawn),
|
||||
[Docker Desktop](https://docs.docker.com/docker-for-mac/kubernetes/):
|
||||
[Docker Desktop](https://docs.docker.com/docker-for-mac/kubernetes/),
|
||||
[kind](https://kind.sigs.k8s.io):
|
||||
for local development
|
||||
|
||||
- [kubicorn](https://github.com/kubicorn/kubicorn),
|
||||
|
||||
@@ -89,3 +89,30 @@
|
||||
- When picking a registry, pay attention to its build system
|
||||
|
||||
(when it has one)
|
||||
|
||||
---
|
||||
|
||||
## Building on the fly
|
||||
|
||||
- Some services can build images on the fly from a repository
|
||||
|
||||
- Example: [ctr.run](https://ctr.run/)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Use ctr.run to automatically build a container image and run it:
|
||||
```bash
|
||||
docker run ctr.run/github.com/jpetazzo/container.training/dockercoins/hasher
|
||||
```
|
||||
|
||||
<!--
|
||||
```longwait Sinatra```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
There might be a long pause before the first layer is pulled,
|
||||
because the API behind `docker pull` doesn't allow to stream build logs, and there is no feedback during the build.
|
||||
|
||||
It is possible to view the build logs by setting up an account on [ctr.run](https://ctr.run/).
|
||||
@@ -427,7 +427,7 @@ nodes and encryption of gossip traffic) were removed for simplicity.
|
||||
|
||||
<!--
|
||||
```wait Synced node info```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- Check the health of the cluster:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Versions installed
|
||||
|
||||
- Kubernetes 1.15.4
|
||||
- Docker Engine 19.03.1
|
||||
- Kubernetes 1.17.1
|
||||
- Docker Engine 19.03.5
|
||||
- Docker Compose 1.24.1
|
||||
|
||||
<!-- ##VERSION## -->
|
||||
@@ -23,6 +23,10 @@ class: extra-details
|
||||
|
||||
## Kubernetes and Docker compatibility
|
||||
|
||||
- Kubernetes 1.17 validates Docker Engine version [up to 19.03](https://github.com/kubernetes/kubernetes/pull/84476)
|
||||
|
||||
*however ...*
|
||||
|
||||
- Kubernetes 1.15 validates Docker Engine versions [up to 18.09](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.15.md#dependencies)
|
||||
<br/>
|
||||
(the latest version when Kubernetes 1.14 was released)
|
||||
@@ -40,5 +44,47 @@ class: extra-details
|
||||
- "Validates" = continuous integration builds with very extensive (and expensive) testing
|
||||
|
||||
- The Docker API is versioned, and offers strong backward-compatibility
|
||||
<br/>
|
||||
(if a client uses e.g. API v1.25, the Docker Engine will keep behaving the same way)
|
||||
|
||||
(If a client uses e.g. API v1.25, the Docker Engine will keep behaving the same way)
|
||||
---
|
||||
|
||||
## Kubernetes versioning and cadence
|
||||
|
||||
- Kubernetes versions are expressed using *semantic versioning*
|
||||
|
||||
(a Kubernetes version is expressed as MAJOR.MINOR.PATCH)
|
||||
|
||||
- There is a new *patch* release whenever needed
|
||||
|
||||
(generally, there is about [2 to 4 weeks](https://github.com/kubernetes/sig-release/blob/master/release-engineering/role-handbooks/patch-release-team.md#release-timing) between patch releases,
|
||||
except when a critical bug or vulnerability is found:
|
||||
in that case, a patch release will follow as fast as possible)
|
||||
|
||||
- There is a new *minor* release approximately every 3 months
|
||||
|
||||
- At any given time, 3 *minor* releases are maintained
|
||||
|
||||
(in other words, a given *minor* release is maintained about 9 months)
|
||||
|
||||
---
|
||||
|
||||
## Kubernetes version compatibility
|
||||
|
||||
*Should my version of `kubectl` match exactly my cluster version?*
|
||||
|
||||
- `kubectl` can be up to one minor version older or newer than the cluster
|
||||
|
||||
(if cluster version is 1.15.X, `kubectl` can be 1.14.Y, 1.15.Y, or 1.16.Y)
|
||||
|
||||
- Things *might* work with larger version differences
|
||||
|
||||
(but they will probably fail randomly, so be careful)
|
||||
|
||||
- This is an example of an error indicating version compability issues:
|
||||
```
|
||||
error: SchemaError(io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus):
|
||||
invalid object doesn't have additional properties
|
||||
```
|
||||
|
||||
- Check [the documentation](https://kubernetes.io/docs/setup/release/version-skew-policy/#kubectl) for the whole story about compatibility
|
||||
|
||||
@@ -66,7 +66,89 @@ class: extra-details
|
||||
|
||||
---
|
||||
|
||||
## A simple volume example
|
||||
## Adding a volume to a Pod
|
||||
|
||||
- We will start with the simplest Pod manifest we can find
|
||||
|
||||
- We will add a volume to that Pod manifest
|
||||
|
||||
- We will mount that volume in a container in the Pod
|
||||
|
||||
- By default, this volume will be an `emptyDir`
|
||||
|
||||
(an empty directory)
|
||||
|
||||
- It will "shadow" the directory where it's mounted
|
||||
|
||||
---
|
||||
|
||||
## Our basic Pod
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-without-volume
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
```
|
||||
|
||||
This is a MVP! (Minimum Viable Pod😉)
|
||||
|
||||
It runs a single NGINX container.
|
||||
|
||||
---
|
||||
|
||||
## Trying the basic pod
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the Pod:
|
||||
```bash
|
||||
kubectl create -f ~/container.training/k8s/nginx-1-without-volume.yaml
|
||||
```
|
||||
|
||||
<!-- ```bash kubectl wait pod/nginx-without-volume --for condition=ready ``` -->
|
||||
|
||||
- Get its IP address:
|
||||
```bash
|
||||
IPADDR=$(kubectl get pod nginx-without-volume -o jsonpath={.status.podIP})
|
||||
```
|
||||
|
||||
- Send a request with curl:
|
||||
```bash
|
||||
curl $IPADDR
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(We should see the "Welcome to NGINX" page.)
|
||||
|
||||
---
|
||||
|
||||
## Adding a volume
|
||||
|
||||
- We need to add the volume in two places:
|
||||
|
||||
- at the Pod level (to declare the volume)
|
||||
|
||||
- at the container level (to mount the volume)
|
||||
|
||||
- We will declare a volume named `www`
|
||||
|
||||
- No type is specified, so it will default to `emptyDir`
|
||||
|
||||
(as the name implies, it will be initialized as an empty directory at pod creation)
|
||||
|
||||
- In that pod, there is also a container named `nginx`
|
||||
|
||||
- That container mounts the volume `www` to path `/usr/share/nginx/html/`
|
||||
|
||||
---
|
||||
|
||||
## The Pod with a volume
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
@@ -86,30 +168,59 @@ spec:
|
||||
|
||||
---
|
||||
|
||||
## A simple volume example, explained
|
||||
## Trying the Pod with a volume
|
||||
|
||||
- We define a standalone `Pod` named `nginx-with-volume`
|
||||
.exercise[
|
||||
|
||||
- In that pod, there is a volume named `www`
|
||||
- Create the Pod:
|
||||
```bash
|
||||
kubectl create -f ~/container.training/k8s/nginx-2-with-volume.yaml
|
||||
```
|
||||
|
||||
- No type is specified, so it will default to `emptyDir`
|
||||
<!-- ```bash kubectl wait pod/nginx-with-volume --for condition=ready ``` -->
|
||||
|
||||
(as the name implies, it will be initialized as an empty directory at pod creation)
|
||||
- Get its IP address:
|
||||
```bash
|
||||
IPADDR=$(kubectl get pod nginx-with-volume -o jsonpath={.status.podIP})
|
||||
```
|
||||
|
||||
- In that pod, there is also a container named `nginx`
|
||||
- Send a request with curl:
|
||||
```bash
|
||||
curl $IPADDR
|
||||
```
|
||||
|
||||
- That container mounts the volume `www` to path `/usr/share/nginx/html/`
|
||||
]
|
||||
|
||||
(We should now see a "403 Forbidden" error page.)
|
||||
|
||||
---
|
||||
|
||||
## A volume shared between two containers
|
||||
## Populating the volume with another container
|
||||
|
||||
- Let's add another container to the Pod
|
||||
|
||||
- Let's mount the volume in *both* containers
|
||||
|
||||
- That container will populate the volume with static files
|
||||
|
||||
- NGINX will then serve these static files
|
||||
|
||||
- To populate the volume, we will clone the Spoon-Knife repository
|
||||
|
||||
- this repository is https://github.com/octocat/Spoon-Knife
|
||||
|
||||
- it's very popular (more than 100K stars!)
|
||||
|
||||
---
|
||||
|
||||
## Sharing a volume between two containers
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-with-volume
|
||||
name: nginx-with-git
|
||||
spec:
|
||||
volumes:
|
||||
- name: www
|
||||
@@ -147,30 +258,84 @@ spec:
|
||||
|
||||
---
|
||||
|
||||
## Sharing a volume, in action
|
||||
## Trying the shared volume
|
||||
|
||||
- Let's try it!
|
||||
- This one will be time-sensitive!
|
||||
|
||||
- We need to catch the Pod IP address *as soon as it's created*
|
||||
|
||||
- Then send a request to it *as fast as possible*
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the pod by applying the YAML file:
|
||||
- Watch the pods (so that we can catch the Pod IP address)
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/nginx-with-volume.yaml
|
||||
kubectl get pods -o wide --watch
|
||||
```
|
||||
|
||||
- Check the IP address that was allocated to our pod:
|
||||
<!--
|
||||
```wait NAME```
|
||||
```tmux split-pane -v```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Shared volume in action
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the pod:
|
||||
```bash
|
||||
kubectl get pod nginx-with-volume -o wide
|
||||
IP=$(kubectl get pod nginx-with-volume -o json | jq -r .status.podIP)
|
||||
kubectl create -f ~/container.training/k8s/nginx-3-with-git.yaml
|
||||
```
|
||||
|
||||
- Access the web server:
|
||||
<!--
|
||||
```bash kubectl wait pod/nginx-with-git --for condition=initialized```
|
||||
```bash IP=$(kubectl get pod nginx-with-git -o jsonpath={.status.podIP})```
|
||||
-->
|
||||
|
||||
- As soon as we see its IP address, access it:
|
||||
```bash
|
||||
curl $IP
|
||||
```
|
||||
|
||||
<!-- ```bash /bin/sleep 5``` -->
|
||||
|
||||
- A few seconds later, the state of the pod will change; access it again:
|
||||
```bash
|
||||
curl $IP
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The first time, we should see "403 Forbidden".
|
||||
|
||||
The second time, we should see the HTML file from the Spoon-Knife repository.
|
||||
|
||||
---
|
||||
|
||||
## Explanations
|
||||
|
||||
- Both containers are started at the same time
|
||||
|
||||
- NGINX starts very quickly
|
||||
|
||||
(it can serve requests immediately)
|
||||
|
||||
- But at this point, the volume is empty
|
||||
|
||||
(NGINX serves "403 Forbidden")
|
||||
|
||||
- The other containers installs git and clones the repository
|
||||
|
||||
(this takes a bit longer)
|
||||
|
||||
- When the other container is done, the volume holds the repository
|
||||
|
||||
(NGINX serves the HTML file)
|
||||
|
||||
---
|
||||
|
||||
## The devil is in the details
|
||||
@@ -183,13 +348,109 @@ spec:
|
||||
|
||||
- That's why we specified `restartPolicy: OnFailure`
|
||||
|
||||
---
|
||||
|
||||
## Inconsistencies
|
||||
|
||||
- There is a short period of time during which the website is not available
|
||||
|
||||
(because the `git` container hasn't done its job yet)
|
||||
|
||||
- This could be avoided by using [Init Containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/)
|
||||
- With a bigger website, we could get inconsistent results
|
||||
|
||||
(we will see a live example in a few sections)
|
||||
(where only a part of the content is ready)
|
||||
|
||||
- In real applications, this could cause incorrect results
|
||||
|
||||
- How can we avoid that?
|
||||
|
||||
---
|
||||
|
||||
## Init Containers
|
||||
|
||||
- We can define containers that should execute *before* the main ones
|
||||
|
||||
- They will be executed in order
|
||||
|
||||
(instead of in parallel)
|
||||
|
||||
- They must all succeed before the main containers are started
|
||||
|
||||
- This is *exactly* what we need here!
|
||||
|
||||
- Let's see one in action
|
||||
|
||||
.footnote[See [Init Containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) documentation for all the details.]
|
||||
|
||||
---
|
||||
|
||||
## Defining Init Containers
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-with-init
|
||||
spec:
|
||||
volumes:
|
||||
- name: www
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /usr/share/nginx/html/
|
||||
initContainers:
|
||||
- name: git
|
||||
image: alpine
|
||||
command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ]
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /www/
|
||||
```
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Trying the init container
|
||||
|
||||
.exercise[
|
||||
|
||||
- Repeat the same operation as earlier
|
||||
|
||||
(try to send HTTP requests as soon as the pod comes up)
|
||||
|
||||
<!--
|
||||
```key ^D```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
- This time, instead of "403 Forbidden" we get a "connection refused"
|
||||
|
||||
- NGINX doesn't start until the git container has done its job
|
||||
|
||||
- We never get inconsistent results
|
||||
|
||||
(a "half-ready" container)
|
||||
|
||||
---
|
||||
|
||||
## Other uses of init containers
|
||||
|
||||
- Load content
|
||||
|
||||
- Generate configuration (or certificates)
|
||||
|
||||
- Database migrations
|
||||
|
||||
- Waiting for other services to be up
|
||||
|
||||
(to avoid flurry of connection errors in main container)
|
||||
|
||||
- etc.
|
||||
|
||||
---
|
||||
|
||||
|
||||
93
slides/k8s/yamldeploy.md
Normal file
93
slides/k8s/yamldeploy.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Deploying with YAML
|
||||
|
||||
- So far, we created resources with the following commands:
|
||||
|
||||
- `kubectl run`
|
||||
|
||||
- `kubectl create deployment`
|
||||
|
||||
- `kubectl expose`
|
||||
|
||||
- We can also create resources directly with YAML manifests
|
||||
|
||||
---
|
||||
|
||||
## `kubectl apply` vs `create`
|
||||
|
||||
- `kubectl create -f whatever.yaml`
|
||||
|
||||
- creates resources if they don't exist
|
||||
|
||||
- if resources already exist, don't alter them
|
||||
<br/>(and display error message)
|
||||
|
||||
- `kubectl apply -f whatever.yaml`
|
||||
|
||||
- creates resources if they don't exist
|
||||
|
||||
- if resources already exist, update them
|
||||
<br/>(to match the definition provided by the YAML file)
|
||||
|
||||
- stores the manifest as an *annotation* in the resource
|
||||
|
||||
---
|
||||
|
||||
## Creating multiple resources
|
||||
|
||||
- The manifest can contain multiple resources separated by `---`
|
||||
|
||||
```yaml
|
||||
kind: ...
|
||||
apiVersion: ...
|
||||
metadata: ...
|
||||
name: ...
|
||||
...
|
||||
---
|
||||
kind: ...
|
||||
apiVersion: ...
|
||||
metadata: ...
|
||||
name: ...
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating multiple resources
|
||||
|
||||
- The manifest can also contain a list of resources
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- kind: ...
|
||||
apiVersion: ...
|
||||
...
|
||||
- kind: ...
|
||||
apiVersion: ...
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deploying dockercoins with YAML
|
||||
|
||||
- We provide a YAML manifest with all the resources for Dockercoins
|
||||
|
||||
(Deployments and Services)
|
||||
|
||||
- We can use it if we need to deploy or redeploy Dockercoins
|
||||
|
||||
.exercise[
|
||||
|
||||
- Deploy or redeploy Dockercoins:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/dockercoins.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(If we deployed Dockercoins earlier, we will see warning messages,
|
||||
because the resources that we created lack the necessary annotation.
|
||||
We can safely ignore them.)
|
||||
|
||||
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- in-person
|
||||
|
||||
@@ -29,23 +31,22 @@ chapters:
|
||||
#- shared/hastyconclusions.md
|
||||
- shared/composedown.md
|
||||
- k8s/concepts-k8s.md
|
||||
- k8s/kubectlget.md
|
||||
-
|
||||
- k8s/kubectlget.md
|
||||
- k8s/kubectlrun.md
|
||||
- k8s/logs-cli.md
|
||||
- shared/declarative.md
|
||||
- k8s/declarative.md
|
||||
- k8s/deploymentslideshow.md
|
||||
-
|
||||
- k8s/kubenet.md
|
||||
- k8s/kubectlexpose.md
|
||||
- k8s/shippingimages.md
|
||||
- k8s/buildshiprun-selfhosted.md
|
||||
- k8s/buildshiprun-dockerhub.md
|
||||
- k8s/ourapponkube.md
|
||||
- k8s/yamldeploy.md
|
||||
-
|
||||
- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
- k8s/setup-k8s.md
|
||||
- k8s/dashboard.md
|
||||
#- k8s/kubectlscale.md
|
||||
@@ -60,35 +61,55 @@ chapters:
|
||||
- k8s/record.md
|
||||
-
|
||||
- k8s/namespaces.md
|
||||
- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
-
|
||||
- k8s/ingress.md
|
||||
- k8s/kustomize.md
|
||||
- k8s/helm.md
|
||||
- k8s/create-chart.md
|
||||
- k8s/helm-intro.md
|
||||
- k8s/helm-chart-format.md
|
||||
- k8s/helm-create-basic-chart.md
|
||||
- k8s/helm-create-better-chart.md
|
||||
- k8s/helm-secrets.md
|
||||
-
|
||||
- k8s/netpol.md
|
||||
- k8s/authn-authz.md
|
||||
-
|
||||
- k8s/podsecuritypolicy.md
|
||||
- k8s/csr-api.md
|
||||
- k8s/openid-connect.md
|
||||
- k8s/podsecuritypolicy.md
|
||||
- k8s/control-plane-auth.md
|
||||
-
|
||||
- k8s/volumes.md
|
||||
- k8s/build-with-docker.md
|
||||
- k8s/build-with-kaniko.md
|
||||
-
|
||||
- k8s/configuration.md
|
||||
-
|
||||
- k8s/logs-centralized.md
|
||||
- k8s/prometheus.md
|
||||
-
|
||||
- k8s/statefulsets.md
|
||||
- k8s/local-persistent-volumes.md
|
||||
- k8s/portworx.md
|
||||
-
|
||||
- k8s/logs-centralized.md
|
||||
- k8s/prometheus.md
|
||||
- k8s/resource-limits.md
|
||||
- k8s/metrics-server.md
|
||||
- k8s/cluster-sizing.md
|
||||
- k8s/horizontal-pod-autoscaler.md
|
||||
-
|
||||
- k8s/extending-api.md
|
||||
- k8s/operators.md
|
||||
- k8s/operators-design.md
|
||||
- k8s/staticpods.md
|
||||
- k8s/owners-and-dependents.md
|
||||
-
|
||||
- k8s/dmuc.md
|
||||
- k8s/multinode.md
|
||||
- k8s/cni.md
|
||||
- k8s/apilb.md
|
||||
- k8s/staticpods.md
|
||||
-
|
||||
- k8s/cluster-upgrade.md
|
||||
- k8s/cluster-backup.md
|
||||
- k8s/cloud-controller-manager.md
|
||||
- k8s/gitworkflows.md
|
||||
-
|
||||
- k8s/whatsnext.md
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
title: |
|
||||
Kubernetes Training
|
||||
Kubernetes
|
||||
|
||||
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
|
||||
#chat: "[Gitter](https://gitter.im/jpetazzo/training-20191008-santamonica)"
|
||||
#chat: "In person!"
|
||||
chat: Slack
|
||||
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
|
||||
chat: "In person!"
|
||||
|
||||
gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://kube-2019-10.container.training/
|
||||
slides: http://2020-01-caen.container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
@@ -23,10 +24,8 @@ chapters:
|
||||
- shared/prereqs.md
|
||||
#- shared/webssh.md
|
||||
- shared/connecting.md
|
||||
- k8s/versions-k8s.md
|
||||
#- k8s/versions-k8s.md
|
||||
- shared/sampleapp.md
|
||||
#- shared/composescale.md
|
||||
#- shared/hastyconclusions.md
|
||||
- shared/composedown.md
|
||||
- k8s/concepts-k8s.md
|
||||
- k8s/kubectlget.md
|
||||
@@ -36,66 +35,67 @@ chapters:
|
||||
- shared/declarative.md
|
||||
- k8s/declarative.md
|
||||
- k8s/deploymentslideshow.md
|
||||
-
|
||||
- k8s/kubenet.md
|
||||
- k8s/kubectlexpose.md
|
||||
- k8s/shippingimages.md
|
||||
#- k8s/buildshiprun-selfhosted.md
|
||||
- k8s/buildshiprun-dockerhub.md
|
||||
- k8s/ourapponkube.md
|
||||
-
|
||||
- k8s/setup-k8s.md
|
||||
- k8s/dashboard.md
|
||||
#- k8s/kubectlscale.md
|
||||
- k8s/scalingdockercoins.md
|
||||
- shared/hastyconclusions.md
|
||||
- k8s/daemonset.md
|
||||
-
|
||||
- k8s/dryrun.md
|
||||
- k8s/daemonset.md
|
||||
- k8s/rollout.md
|
||||
- k8s/healthchecks.md
|
||||
- k8s/healthchecks-more.md
|
||||
#- k8s/healthchecks-more.md
|
||||
- k8s/record.md
|
||||
- # DAY 2
|
||||
- k8s/namespaces.md
|
||||
- k8s/yamldeploy.md
|
||||
#- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
- k8s/kubectlproxy.md
|
||||
- k8s/ingress.md
|
||||
-
|
||||
- k8s/logs-centralized.md
|
||||
- k8s/prometheus.md
|
||||
-
|
||||
- k8s/volumes.md
|
||||
#- k8s/build-with-docker.md
|
||||
#- k8s/build-with-kaniko.md
|
||||
- k8s/configuration.md
|
||||
-
|
||||
- k8s/resource-limits.md
|
||||
- k8s/metrics-server.md
|
||||
- k8s/cluster-sizing.md
|
||||
- k8s/horizontal-pod-autoscaler.md
|
||||
-
|
||||
- k8s/kustomize.md
|
||||
- k8s/helm-intro.md
|
||||
- k8s/helm-chart-format.md
|
||||
- k8s/helm-create-basic-chart.md
|
||||
- k8s/helm-create-better-chart.md
|
||||
- k8s/helm-secrets.md
|
||||
- # DAY 3
|
||||
- k8s/netpol.md
|
||||
- k8s/authn-authz.md
|
||||
#- k8s/csr-api.md
|
||||
#- k8s/openid-connect.md
|
||||
#- k8s/podsecuritypolicy.md
|
||||
-
|
||||
- k8s/kustomize.md
|
||||
- k8s/helm.md
|
||||
- k8s/create-chart.md
|
||||
- k8s/create-more-charts.md
|
||||
-
|
||||
- k8s/extending-api.md
|
||||
- k8s/operators.md
|
||||
- k8s/operators-design.md
|
||||
- k8s/owners-and-dependents.md
|
||||
- k8s/dashboard.md
|
||||
- k8s/logs-centralized.md
|
||||
- k8s/prometheus.md
|
||||
-
|
||||
- k8s/statefulsets.md
|
||||
- k8s/local-persistent-volumes.md
|
||||
- k8s/portworx.md
|
||||
- # CONCLUSION
|
||||
- k8s/whatsnext.md
|
||||
-
|
||||
- k8s/extending-api.md
|
||||
- k8s/operators.md
|
||||
- k8s/operators-design.md
|
||||
- # END
|
||||
- k8s/lastwords-admin.md
|
||||
- k8s/links.md
|
||||
- shared/thankyou.md
|
||||
# EXTRA
|
||||
#- k8s/staticpods.md
|
||||
#- k8s/owners-and-dependents.md
|
||||
#- k8s/gitworkflows.md
|
||||
#- k8s/csr-api.md
|
||||
#- k8s/openid-connect.md
|
||||
#- k8s/podsecuritypolicy.md
|
||||
#- k8s/setup-k8s.md
|
||||
#- k8s/dryrun.md
|
||||
@@ -1,10 +1,10 @@
|
||||
## Intros
|
||||
|
||||
- Hello! I'm .emoji[🐳] Jérôme ([@jpetazzo](https://twitter.com/jpetazzo))
|
||||
- Hello! I'm Jérôme ([@jpetazzo](https://twitter.com/jpetazzo), Enix SAS)
|
||||
|
||||
- The workshop will run from 9am to 5pm
|
||||
- The training will run from 9am to 5pm
|
||||
|
||||
- There will be a lunch break at noon
|
||||
- There will be a lunch break at 12:30pm
|
||||
|
||||
(And coffee breaks!)
|
||||
|
||||
@@ -12,4 +12,3 @@
|
||||
|
||||
- *Especially when you see full screen container pictures!*
|
||||
|
||||
- Live feedback, questions, help: @@CHAT@@
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# transforms a YAML manifest into a HTML workshop file
|
||||
|
||||
import glob
|
||||
@@ -20,12 +20,19 @@ def anchor(title):
|
||||
return "toc-" + title
|
||||
|
||||
|
||||
def interstitials_generator():
|
||||
images = [url.strip() for url in open("interstitials.txt") if url.strip()]
|
||||
while True:
|
||||
for image in images:
|
||||
yield image
|
||||
interstitials = interstitials_generator()
|
||||
class Interstitials(object):
|
||||
|
||||
def __init__(self):
|
||||
self.index = 0
|
||||
self.images = [url.strip() for url in open("interstitials.txt") if url.strip()]
|
||||
|
||||
def next(self):
|
||||
index = self.index % len(self.images)
|
||||
self.index += 1
|
||||
return self.images[index]
|
||||
|
||||
|
||||
interstitials = Interstitials()
|
||||
|
||||
|
||||
def insertslide(markdown, title):
|
||||
@@ -54,7 +61,7 @@ class: pic
|
||||
name: {anchor}
|
||||
class: title
|
||||
|
||||
{title}
|
||||
{title}
|
||||
|
||||
.nav[
|
||||
[Previous section](#{previouslink})
|
||||
@@ -80,7 +87,16 @@ def flatten(titles):
|
||||
|
||||
|
||||
def generatefromyaml(manifest, filename):
|
||||
manifest = yaml.load(manifest)
|
||||
manifest = yaml.safe_load(manifest)
|
||||
|
||||
if "zip" not in manifest:
|
||||
if manifest["slides"].endswith('/'):
|
||||
manifest["zip"] = manifest["slides"] + "slides.zip"
|
||||
else:
|
||||
manifest["zip"] = manifest["slides"] + "/slides.zip"
|
||||
|
||||
if "html" not in manifest:
|
||||
manifest["html"] = filename + ".html"
|
||||
|
||||
markdown, titles = processchapter(manifest["chapters"], filename)
|
||||
logging.debug("Found {} titles.".format(len(titles)))
|
||||
@@ -110,7 +126,10 @@ def generatefromyaml(manifest, filename):
|
||||
html = html.replace("@@CHAT@@", manifest["chat"])
|
||||
html = html.replace("@@GITREPO@@", manifest["gitrepo"])
|
||||
html = html.replace("@@SLIDES@@", manifest["slides"])
|
||||
html = html.replace("@@ZIP@@", manifest["zip"])
|
||||
html = html.replace("@@HTML@@", manifest["html"])
|
||||
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
|
||||
html = html.replace("@@SLIDENUMBERPREFIX@@", manifest.get("slidenumberprefix", ""))
|
||||
return html
|
||||
|
||||
|
||||
@@ -153,8 +172,6 @@ def gentoc(tree, path=()):
|
||||
# Returns: (epxandedmarkdown,[list of titles])
|
||||
# The list of titles can be nested.
|
||||
def processchapter(chapter, filename):
|
||||
if isinstance(chapter, unicode):
|
||||
return processchapter(chapter.encode("utf-8"), filename)
|
||||
if isinstance(chapter, str):
|
||||
if "\n" in chapter:
|
||||
titles = re.findall("^# (.*)", chapter, re.MULTILINE)
|
||||
@@ -178,14 +195,14 @@ try:
|
||||
if "REPOSITORY_URL" in os.environ:
|
||||
repo = os.environ["REPOSITORY_URL"]
|
||||
else:
|
||||
repo = subprocess.check_output(["git", "config", "remote.origin.url"])
|
||||
repo = subprocess.check_output(["git", "config", "remote.origin.url"]).decode("ascii")
|
||||
repo = repo.strip().replace("git@github.com:", "https://github.com/")
|
||||
if "BRANCH" in os.environ:
|
||||
branch = os.environ["BRANCH"]
|
||||
else:
|
||||
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
||||
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("ascii")
|
||||
branch = branch.strip()
|
||||
base = subprocess.check_output(["git", "rev-parse", "--show-prefix"])
|
||||
base = subprocess.check_output(["git", "rev-parse", "--show-prefix"]).decode("ascii")
|
||||
base = base.strip().strip("/")
|
||||
urltemplate = ("{repo}/tree/{branch}/{base}/{filename}"
|
||||
.format(repo=repo, branch=branch, base=base, filename="{}"))
|
||||
@@ -193,12 +210,12 @@ except:
|
||||
logging.exception("Could not generate repository URL; generating local URLs instead.")
|
||||
urltemplate = "file://{pwd}/{filename}".format(pwd=os.environ["PWD"], filename="{}")
|
||||
try:
|
||||
commit = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
|
||||
commit = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode("ascii")
|
||||
except:
|
||||
logging.exception("Could not figure out HEAD commit.")
|
||||
commit = "??????"
|
||||
try:
|
||||
dirtyfiles = subprocess.check_output(["git", "status", "--porcelain"])
|
||||
dirtyfiles = subprocess.check_output(["git", "status", "--porcelain"]).decode("ascii")
|
||||
except:
|
||||
logging.exception("Could not figure out repository cleanliness.")
|
||||
dirtyfiles = "?? git status --porcelain failed"
|
||||
|
||||
1
slides/runtime.txt
Normal file
1
slides/runtime.txt
Normal file
@@ -0,0 +1 @@
|
||||
3.7
|
||||
@@ -40,7 +40,7 @@ class: extra-details
|
||||
|
||||
<!--
|
||||
```wait units of work done```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
@@ -75,7 +75,7 @@ Tip: use `^S` and `^Q` to pause/resume log output.
|
||||
```bash top```
|
||||
|
||||
```wait Tasks```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
- run `vmstat 1` to see I/O usage (si/so/bi/bo)
|
||||
@@ -85,7 +85,7 @@ Tip: use `^S` and `^Q` to pause/resume log output.
|
||||
```bash vmstat 1```
|
||||
|
||||
```wait memory```
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -4,7 +4,12 @@ class: in-person
|
||||
|
||||
.exercise[
|
||||
|
||||
- Log into the first VM (`node1`) with your SSH client
|
||||
- Log into the first VM (`node1`) with your SSH client:
|
||||
```bash
|
||||
ssh `user`@`A.B.C.D`
|
||||
```
|
||||
|
||||
(Replace `user` and `A.B.C.D` with the user and IP address provided to you)
|
||||
|
||||
<!--
|
||||
```bash
|
||||
@@ -18,16 +23,13 @@ done
|
||||
```
|
||||
-->
|
||||
|
||||
- Check that you can SSH (without password) to `node2`:
|
||||
```bash
|
||||
ssh node2
|
||||
```
|
||||
- Type `exit` or `^D` to come back to `node1`
|
||||
|
||||
<!-- ```bash exit``` -->
|
||||
|
||||
]
|
||||
|
||||
You should see a prompt looking like this:
|
||||
```
|
||||
[A.B.C.D] (...) user@node1 ~
|
||||
$
|
||||
```
|
||||
If anything goes wrong — ask for help!
|
||||
|
||||
---
|
||||
@@ -52,6 +54,20 @@ If anything goes wrong — ask for help!
|
||||
|
||||
---
|
||||
|
||||
## For a consistent Kubernetes experience ...
|
||||
|
||||
- If you are using your own Kubernetes cluster, you can use [shpod](https://github.com/jpetazzo/shpod)
|
||||
|
||||
- `shpod` provides a shell running in a pod on your own cluster
|
||||
|
||||
- It comes with many tools pre-installed (helm, stern...)
|
||||
|
||||
- These tools are used in many exercises in these slides
|
||||
|
||||
- `shpod` also gives you completion and a fancy prompt
|
||||
|
||||
---
|
||||
|
||||
class: self-paced
|
||||
|
||||
## Get your own Docker nodes
|
||||
|
||||
@@ -58,6 +58,28 @@ Misattributed to Benjamin Franklin
|
||||
|
||||
---
|
||||
|
||||
## Navigating slides
|
||||
|
||||
- Use arrows to move to next/previous slide
|
||||
|
||||
(up, down, left, right, page up, page down)
|
||||
|
||||
- Type a slide number + ENTER to go to that slide
|
||||
|
||||
- The slide number is also visible in the URL bar
|
||||
|
||||
(e.g. .../#123 for slide 123)
|
||||
|
||||
- Slides will remain online so you can review them later if needed
|
||||
|
||||
- You can download the slides using that URL:
|
||||
|
||||
@@ZIP@@
|
||||
|
||||
(then open the file `@@HTML@@`)
|
||||
|
||||
---
|
||||
|
||||
class: in-person
|
||||
|
||||
## Where are we going to run our containers?
|
||||
|
||||
@@ -343,7 +343,7 @@ class: extra-details
|
||||
- Stop the application by hitting `^C`
|
||||
|
||||
<!--
|
||||
```keys ^C```
|
||||
```key ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
@@ -11,5 +11,5 @@ class: title, in-person
|
||||
@@TITLE@@<br/></br>
|
||||
|
||||
.footnote[
|
||||
**Slides[:](https://www.youtube.com/watch?v=h16zyxiwDLY) @@SLIDES@@**
|
||||
**Slides: @@SLIDES@@**
|
||||
]
|
||||
|
||||
@@ -267,7 +267,7 @@ class: extra-details
|
||||
|
||||
- Switch back to `node1` (with `exit`, `Ctrl-D` ...)
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
<!-- ```key ^D``` -->
|
||||
|
||||
- View the cluster from `node1`, which is a manager:
|
||||
```bash
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
```
|
||||
|
||||
<!-- ```wait User-Agent``` -->
|
||||
<!-- ```keys ^C``` -->
|
||||
<!-- ```key ^C``` -->
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class: elk-manual
|
||||
```
|
||||
|
||||
<!-- ```wait "message" => "ok"``` -->
|
||||
<!-- ```keys ^C``` -->
|
||||
<!-- ```key ^C``` -->
|
||||
|
||||
]
|
||||
|
||||
@@ -266,7 +266,7 @@ The test message should show up in the logstash container logs.
|
||||
```
|
||||
|
||||
<!-- ```wait Detected task failure``` -->
|
||||
<!-- ```keys ^C``` -->
|
||||
<!-- ```key ^C``` -->
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
var slideshow = remark.create({
|
||||
ratio: '16:9',
|
||||
highlightSpans: true,
|
||||
slideNumberFormat: '@@SLIDENUMBERPREFIX@@%current%/%total%',
|
||||
excludedClasses: [@@EXCLUDE@@]
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user