Compare commits

..

103 Commits

Author SHA1 Message Date
Jerome Petazzoni
86828caf31 fix-redirects.sh: adding forced redirect 2020-04-07 16:47:57 -05:00
Jérôme Petazzoni
ffaca8925b Merge pull request #546 from arthurlogilab/patch-7
[cluster-backup] add bivac
2020-02-08 08:42:54 -06:00
Jérôme Petazzoni
4e27c60e07 Merge pull request #542 from arthurlogilab/patch-3
[extending-api] link to video, not playlist
2020-02-08 08:41:14 -06:00
Jérôme Petazzoni
c38e169b61 Merge pull request #540 from arthurlogilab/patch-1
[cluster-sizing.md] add link to Kiyot project page
2020-02-08 08:40:18 -06:00
Jérôme Petazzoni
061451ec12 Merge pull request #545 from arthurlogilab/patch-6
[slides/cni] plugins : point to README
2020-02-08 08:38:56 -06:00
Arthur Lutz
6139a9a1fa [cluster-backup] add bivac
Backup Interface for Volumes Attached to Containers
2020-02-07 16:59:00 +01:00
Arthur Lutz
a9f0ee93d8 [slides/cni] plugins : point to README 2020-02-07 11:49:25 +01:00
Arthur Lutz
0fc8b8e884 [extending-api] link to video, not playlist 2020-02-06 17:25:38 +01:00
Arthur Lutz
b9667365a4 [cluster-sizing.md] add link to Kiyot project page 2020-02-06 16:02:46 +01:00
Jerome Petazzoni
dde20a81bc Shuffle 2020-02-05 11:44:45 -06:00
Jerome Petazzoni
b17c2150a3 Merge branch 'master' into 2020-02-enix 2020-02-05 09:32:21 -06:00
Jerome Petazzoni
1414b74224 typos 2020-02-05 09:32:19 -06:00
Jerome Petazzoni
a3da2615ff typos 2020-02-05 09:31:13 -06:00
Jerome Petazzoni
d0202963b7 typos 2020-02-05 05:05:19 -06:00
Jerome Petazzoni
bad25bfb80 Merge branch 'master' into 2020-02-enix 2020-02-05 05:02:50 -06:00
Jerome Petazzoni
9a1ea0f1bd Expand info on kube-ps1 2020-02-05 04:29:04 -06:00
Jerome Petazzoni
834fe6c617 Merge branch 'master' into 2020-02-enix 2020-02-05 02:51:34 -06:00
Jerome Petazzoni
67ac03e76e Highlight $IP 2020-02-05 02:51:12 -06:00
Jerome Petazzoni
783d2783dc Merge branch '2020-02-enix' of github.com:jpetazzo/container.training into 2020-02-enix 2020-02-05 01:52:13 -06:00
Jerome Petazzoni
b0fa8d497e Add restaurant address 2020-02-05 01:52:03 -06:00
Julien Girardin
97530166d1 Copy introduction of day 2 2020-02-05 08:20:36 +01:00
Julien Girardin
360f74f555 Something like last push for day 3 2020-02-04 22:48:00 +01:00
Jerome Petazzoni
18b7ee9e3d Merge branch '2020-02-enix' of github.com:jpetazzo/container.training into 2020-02-enix 2020-02-04 06:34:20 -06:00
Jerome Petazzoni
7afab5619f Merge branch 'master' into 2020-02-enix 2020-02-04 06:34:07 -06:00
Jerome Petazzoni
38725cf3da typo 2020-02-04 06:33:54 -06:00
Julien Girardin
1db1bf3885 A batch of fix for 3rd day 2020-02-03 18:41:18 +01:00
Jerome Petazzoni
822fa8443b Add WiFi info 2020-02-03 01:48:51 -06:00
Jerome Petazzoni
10937f7740 Add @zempashi's emoji and link 2020-02-02 13:55:17 -06:00
Jerome Petazzoni
ad94cdab2d Last tweaks 2020-02-02 10:45:00 -06:00
Jerome Petazzoni
2495040007 Merge branch 'master' into 2020-02-enix 2020-02-02 10:21:17 -06:00
Jerome Petazzoni
3e98148e3f Refactor useful information to about-slide.md
prereqs.md can vary from a session to another, so I moved
the common information to about-slides.md (which should be
included all the time).
2020-02-02 10:20:39 -06:00
Jerome Petazzoni
8fab9999f2 Merge branch 'master' into 2020-02-enix 2020-02-02 09:35:30 -06:00
Jerome Petazzoni
b988c28b41 Extend section on API extension
Mention the aggregation layer.

Add an example of CRD.
2020-02-02 09:24:17 -06:00
Jerome Petazzoni
583933c492 Add kubectl delete -f and --prune 2020-02-02 08:16:28 -06:00
Jerome Petazzoni
ebadc1ca59 Upgrade the cluster upgrade chapter
Add information about version skew.
Better explain what's good/bad to do.
2020-02-02 05:41:01 -06:00
Jerome Petazzoni
3ecbec85de Reorg day 2 content 2020-02-02 04:43:28 -06:00
Julien Girardin
10024ff021 Merge pull request #539 from jpetazzo/review-j3
More tweaks
2020-01-31 22:16:59 +01:00
Jerome Petazzoni
17eea45536 merge 2020-01-31 12:56:11 -06:00
Jerome Petazzoni
9089157367 Tweaks / formating / English 2020-01-31 12:37:11 -06:00
Jérôme Petazzoni
7f85c645f0 Update helm-intro.md 2020-01-31 03:53:28 -06:00
Julien Girardin
8599c572a0 Merge pull request #538 from jpetazzo/review-j3
Review j3
2020-01-31 08:55:11 +01:00
Jerome Petazzoni
8038d5ebff Many small fixes + update on registries 2020-01-30 15:40:13 -06:00
Jerome Petazzoni
ed5009c769 Tweaks after Caen 2020-01-30 14:44:44 -06:00
Jerome Petazzoni
e2f3034a96 Fix container picture generator 2020-01-30 01:11:09 -06:00
Julien Girardin
e569388618 Advanced rollout and security 2020-01-29 18:12:26 +01:00
Jerome Petazzoni
646a0f7ee2 First round of reviews 2020-01-28 13:40:38 -06:00
Jerome Petazzoni
3a1549e3a4 C'est la merge, chef 2020-01-28 12:41:59 -06:00
Julien Girardin
54a9ba25b3 Add Exercice for promtheus and open-telemetry 2020-01-28 16:26:39 +01:00
Julien Girardin
128a5a2340 Opentelemetry and a few things on prometheus 2020-01-28 16:16:36 +01:00
Julien Girardin
0f34f037bf Second batch for day 3 2020-01-27 18:10:34 +01:00
Jerome Petazzoni
8933e6aa1b Big Helm update
Revamp most of the Helm content:
- overview of Helm moved to helm-intro.md
- explanation of chart format in helm-chart-format.md
- the very crude chart example is now in helm-create-basic-chart.md
- the more advanced chart (with templates etc) is now in helm-create-better-chart.md
- deep dive into Helm internals (how it stores it's data) in helm-secrets.md

This is all for Helm 3. Helm 2 is not supported anymore.
2020-01-27 07:26:54 -06:00
Julien Girardin
c0d735ade2 More on day 3 2020-01-27 13:42:40 +01:00
Julien Girardin
3914160d28 starting filling day 3 2020-01-27 13:42:02 +01:00
Jerome Petazzoni
784b2a3e4e Big update to autopilot
Autopilot can now continue when errors happen, and it writes
success/failure of each snippet in a log file for later review.

Also added e2e.sh to provision a test environment and start
the remote tmux instance.
2020-01-20 14:23:20 -06:00
Jerome Petazzoni
f3bbd6377b Merge branch 'helm-3' 2020-01-20 02:45:05 -06:00
Jerome Petazzoni
04d3a7b360 Fix up slide about operators limitations 2020-01-19 11:34:18 -06:00
Jerome Petazzoni
a32df01165 Revamp operator example
Use Elastic Cloud for Kubernetes instead of the
UPMC Enterprises operator.
2020-01-19 11:32:04 -06:00
Jerome Petazzoni
ba323cb4e6 Update Portworx 2020-01-18 12:06:04 -06:00
Jerome Petazzoni
745a435a1a Fix linebreak on cronjob 2020-01-18 11:51:57 -06:00
Jerome Petazzoni
db276af182 Update Consul
Bump up Consul version to 1.6.

Change persistent consul demo; instead of a separate namespace,
use a different label. This way, the two manifests can be more
similar; and this simplifies the demo flow.
2020-01-18 11:33:02 -06:00
Jerome Petazzoni
87462939d9 Update dashboard to version 2.0 2020-01-18 11:12:33 -06:00
Jerome Petazzoni
7d6ab6974d Big autopilot update
'keys' does not handle special keys (like ^J) anymore.
Instead, we should use `key`, which will pass its entire
argument to tmux, without any processing. It is therefore
possible to do something like:

```key ^C```

Or

```key Escape```

Most (if not all) calls to special keys have been
converted to use 'key' instead of 'keys'.

Action ```copypaste``` has been deprecated in favor
of three separate actions:

```copy REGEX``` (searches the regex in the active pane,
and if found, places it in an internal clipboard)

```paste``` (inserts the content of the clipboard as
keystrokes)

```check``` (forces a status check)

Also, a 'tmux' command has been added. It allows to
do stuff like:

```tmux split-pane -v```
2020-01-18 09:49:18 -06:00
Jerome Petazzoni
ae606b9c40 Merge branch 'master' into helm-3 2020-01-18 03:04:24 -06:00
Jerome Petazzoni
da9921d68a Update explanations for canary 2020-01-18 02:36:41 -06:00
Jerome Petazzoni
3e9a939578 Add traffic split / canary for Traefik 2020-01-17 17:07:43 -06:00
Jerome Petazzoni
328a2edaaf Add slide about number of nodes in a cluster 2020-01-17 14:17:18 -06:00
Jerome Petazzoni
1f826d7993 Add slide about version skew 2020-01-17 12:28:27 -06:00
Jerome Petazzoni
cff9cbdfbb Add slide about versioning and cadence 2020-01-17 12:01:20 -06:00
Jerome Petazzoni
3ea6b730c8 Update the Prometheus install instructions 2020-01-17 11:46:58 -06:00
Jerome Petazzoni
1c6c76162f Add link to zip file 2020-01-17 10:11:12 -06:00
Jerome Petazzoni
52bafdb57e Update Helm chapter to Helm 3 2020-01-17 08:21:23 -06:00
Jerome Petazzoni
c4d9e6b3e1 Update deployment scripts to install Helm 3 2020-01-17 04:45:06 -06:00
Jerome Petazzoni
5160dd39a0 Add mention to ctr.run 2020-01-14 15:43:00 -06:00
Jerome Petazzoni
3c1220be83 Replace 1.1 with 127.1
This avoids pinging an external machine
2020-01-13 17:43:24 -06:00
Jerome Petazzoni
6a814cf039 Upgrade slide generator to python3; generate a zip file too 2020-01-12 13:28:48 -06:00
Jerome Petazzoni
1385a1bae2 Add QCON and Enix High-Five 2019-12-20 11:41:46 -06:00
Jerome Petazzoni
68a6546276 Fun with flags
Add flags in front of 'coming soon' workshops.
2019-12-20 11:41:37 -06:00
Jerome Petazzoni
8a2ca450ee Add extended Helm content 2019-12-10 14:21:09 -06:00
Jerome Petazzoni
6e8ac173e0 Add kube adm content to self-paced deck
/cc @bretfisher
2019-12-10 14:19:56 -06:00
Jerome Petazzoni
97e68ae185 Support : in titles 2019-12-06 16:25:16 -06:00
Jérôme Petazzoni
148ddd5174 Merge pull request #535 from jpetazzo/slides-docker-pods-anatomy
Slides docker pods anatomy
2019-12-06 22:25:40 +01:00
Jerome Petazzoni
e8eb11e257 Tweak Pods Anatomy slides for inclusion in master 2019-12-06 15:19:04 -06:00
Jérôme Petazzoni
fe9b56572e Merge pull request #534 from jpetazzo/slides-docker-init-systems
Slides docker init systems
2019-12-06 21:38:37 +01:00
Jerome Petazzoni
7281ca3ca0 Tweak content for inclusion in master branch 2019-12-06 14:16:48 -06:00
Julien Girardin
34a17aa097 Add a Pod anatomy set of slides 2019-12-06 17:15:21 +01:00
Julien Girardin
b37dd85eff Add Init_system slides 2019-12-06 11:03:48 +01:00
Jerome Petazzoni
4811420d55 Update Docker Mastery referral code 2019-11-29 12:48:59 -06:00
Jerome Petazzoni
a824afec85 Add shortlinks for uDemy course 2019-11-29 09:34:25 -06:00
Jerome Petazzoni
89d9fcb1c4 Fix port range # 2019-11-21 12:54:23 -06:00
Jérôme Petazzoni
5b488fbe62 Update Installing_Docker.md 2019-11-19 09:35:46 -06:00
Jerome Petazzoni
6d01a9d813 Add commands to prep portworx; make postgresql work on PKS 2019-11-19 07:40:01 -06:00
Jerome Petazzoni
cb81469170 Move storage class to portworx manifest 2019-11-19 06:58:49 -06:00
Jerome Petazzoni
c595a337e4 Rewrite services section
Improve the order when introducing ClusterIP, LoadBalancer, NodePort.
Explain the deal with ExternalIP and ExternalName, and reword the
Ingress slide.
2019-11-19 06:51:39 -06:00
Jerome Petazzoni
03d2d0bc5d kubectl is the new SSH 2019-11-18 16:47:10 -06:00
Jerome Petazzoni
2c46106792 Add explanations to navigate slides 2019-11-18 13:53:54 -06:00
Jerome Petazzoni
291d2a6c92 Add note about DNS integration 2019-11-18 13:30:09 -06:00
Jerome Petazzoni
f73fb92832 Put pods before services
The flow is better this way, since we can introduce pods
just after seeing them in kubectl describe node.

Also, add some extra info when we curl the Kubernetes API.
2019-11-18 12:57:26 -06:00
Jerome Petazzoni
e9e2fa0e50 Fix YAML formatting 2019-11-18 09:04:18 -06:00
Jerome Petazzoni
a0162d37f1 Add explanations to the node/pod diagram 2019-11-15 08:49:57 -06:00
Jerome Petazzoni
80356d92cb Update after work session with @zempashi 2019-11-06 08:26:13 -06:00
Jerome Petazzoni
e9b22c5074 Merge branch 'master' into 2020-02-enix 2019-11-06 07:18:38 -06:00
Jerome Petazzoni
ed55c72366 Free up most of day 3 2019-10-25 12:44:12 -05:00
Jerome Petazzoni
a1a2cccfef First draft for Feb 2020 content 2019-10-06 10:28:07 -05:00
117 changed files with 8247 additions and 1773 deletions

1
.gitignore vendored
View File

@@ -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
View 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

15
k8s/coffee-1.yaml Normal file
View File

@@ -0,0 +1,15 @@
apiVersion: apiextensions.k8s.io/v1alpha1
kind: CustomResourceDefinition
metadata:
name: coffees.container.training
spec:
group: container.training
version: v1alpha1
scope: Namespaced
names:
plural: coffees
singular: coffee
kind: Coffee
shortNames:
- cof

32
k8s/coffee-2.yaml Normal file
View File

@@ -0,0 +1,32 @@
apiVersion: apiextensions.k8s.io/v1alpha1
kind: CustomResourceDefinition
metadata:
name: coffees.container.training
spec:
group: container.training
version: v1alpha1
scope: Namespaced
names:
plural: coffees
singular: coffee
kind: Coffee
shortNames:
- cof
additionalPrinterColumns:
- JSONPath: .spec.taste
description: Subjective taste of that kind of coffee bean
name: Taste
type: string
- JSONPath: .metadata.creationTimestamp
name: Age
type: date
validation:
openAPIV3Schema:
properties:
spec:
required:
- taste
properties:
taste:
description: Subjective taste of that kind of coffee bean
type: string

29
k8s/coffees.yaml Normal file
View File

@@ -0,0 +1,29 @@
---
kind: Coffee
apiVersion: container.training/v1alpha1
metadata:
name: arabica
spec:
taste: strong
---
kind: Coffee
apiVersion: container.training/v1alpha1
metadata:
name: robusta
spec:
taste: stronger
---
kind: Coffee
apiVersion: container.training/v1alpha1
metadata:
name: liberica
spec:
taste: smoky
---
kind: Coffee
apiVersion: container.training/v1alpha1
metadata:
name: excelsa
spec:
taste: fruity

View File

@@ -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"

69
k8s/eck-cerebro.yaml Normal file
View 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}
}
}
]

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -13,7 +13,7 @@ spec:
mountPath: /usr/share/nginx/html/
- name: git
image: alpine
command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ]
command: [ "sh", "-c", "apk add git && git clone https://github.com/octocat/Spoon-Knife /www" ]
volumeMounts:
- name: www
mountPath: /www/

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

10
prepare-vms/e2e.sh Executable file
View 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

View File

@@ -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
}

View File

@@ -61,6 +61,6 @@ TAG=$PREFIX-$SETTINGS
--count $((3*$STUDENTS))
./workshopctl deploy $TAG
./workshopctl kube $TAG 1.14.6
./workshopctl kube $TAG 1.16.6
./workshopctl cards $TAG

69
slides/1.yml Normal file
View File

@@ -0,0 +1,69 @@
title: |
Jour 1
Fondamentaux
Conteneurs & Docker
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/enix/formation-highfive-202002)"
gitrepo: github.com/jpetazzo/container.training
slides: http://2020-02-enix.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- containers/intro.md
- shared/about-slides.md
- shared/toc.md
-
- containers/Docker_Overview.md
#- containers/Docker_History.md
- containers/Training_Environment.md
#- containers/Installing_Docker.md
- containers/First_Containers.md
- containers/Background_Containers.md
- containers/Start_And_Attach.md
- containers/Initial_Images.md
-
- containers/Building_Images_Interactively.md
- containers/Building_Images_With_Dockerfiles.md
- containers/Cmd_And_Entrypoint.md
- containers/Copying_Files_During_Build.md
- containers/Exercise_Dockerfile_Basic.md
-
- containers/Naming_And_Inspecting.md
#- containers/Labels.md
- containers/Getting_Inside.md
#- containers/Resource_Limits.md
- containers/Multi_Stage_Builds.md
- containers/Publishing_To_Docker_Hub.md
- containers/Dockerfile_Tips.md
- containers/Exercise_Dockerfile_Advanced.md
-
- containers/Container_Networking_Basics.md
#- containers/Network_Drivers.md
- containers/Container_Network_Model.md
#- containers/Connecting_Containers_With_Links.md
#- containers/Ambassadors.md
- containers/Local_Development_Workflow.md
#- containers/Windows_Containers.md
#- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- containers/Exercise_Composefile.md
#- containers/Docker_Machine.md
#- containers/Advanced_Dockerfiles.md
#- containers/Application_Configuration.md
#- containers/Logging.md
#- containers/Namespaces_Cgroups.md
#- containers/Copy_On_Write.md
#- containers/Containers_From_Scratch.md
#- containers/Container_Engines.md
#- containers/Ecosystem.md
#- containers/Orchestration_Overview.md
-
- shared/thankyou.md
- containers/links.md

57
slides/2.yml Normal file
View File

@@ -0,0 +1,57 @@
title: |
Jour 2
Fondamentaux
Orchestration
& Kubernetes
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/enix/formation-highfive-202002)"
gitrepo: github.com/jpetazzo/container.training
slides: http://2020-02-enix.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
-
- shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
- shared/composedown.md
- k8s/concepts-k8s.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-dockerhub.md
- k8s/ourapponkube.md
- k8s/yamldeploy.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
-
- k8s/rollout.md
#- k8s/dryrun.md
- k8s/healthchecks.md
#- k8s/healthchecks-more.md
#- k8s/record.md
#- k8s/dashboard.md
- k8s/ingress.md
-
- shared/thankyou.md

81
slides/3.yml Normal file
View File

@@ -0,0 +1,81 @@
title: |
Jour 3
Méthodologies DevOps
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/enix/formation-highfive-202002)"
gitrepo: github.com/jpetazzo/container.training
slides: http://2020-02-enix.container.training/
exclude:
- self-paced
- hide-exercise
chapters:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
-
- shared/prereqs.md
- shared/connecting.md
# Bien démarrer en local (minikube, kind)
- shared/sampleapp.md
- k8s/software-dev-banalities.md
- k8s/on-desktop.md
- k8s/volumes.md
- k8s/namespaces.md
- k8s/localkubeconfig.md
- k8s/accessinternal.md
- k8s/testing.md
-
- k8s/configuration.md
- k8s/sealed-secrets.md
- k8s/kustomize.md
- k8s/helm-intro.md
- k8s/helm-chart-format.md
- k8s/helm-secrets.md
-
- k8s/shippingimages.md
- k8s/registries.md
- k8s/stop-manual.md
- k8s/ci-cd.md
- k8s/exercise-ci-build.md
- k8s/kaniko.md
- k8s/exercise-ci-kaniko.md
- k8s/rollout.md
- k8s/advanced-rollout.md
- k8s/devs-and-ops-joined-topics.md
-
- k8s/prometheus-endpoint.md
- k8s/exercise-prometheus.md
- k8s/opentelemetry.md
- k8s/exercise-opentelemetry.md
- k8s/kubernetes-security.md
#- |
# # (Automatiser)
#- |
# # Fabrication d'image
#- |
# # Skaffold
#- |
# # Registries
#- |
# # Gitlab, CI
#- |
# # ROllout avancé, blue green, canary
#- |
# # Monitoring applicatif
#- |
# # Prometheus Grafana
#- |
# # Telemetry
-
- shared/thankyou.md

40
slides/4.yml Normal file
View File

@@ -0,0 +1,40 @@
title: |
Jour 4
Kubernetes Avancé
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/enix/formation-highfive-202002)"
gitrepo: github.com/jpetazzo/container.training
slides: http://2020-02-enix.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
-
- k8s/netpol.md
- k8s/authn-authz.md
-
- k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md
-
- k8s/resource-limits.md
- k8s/metrics-server.md
- k8s/cluster-sizing.md
- k8s/horizontal-pod-autoscaler.md
-
- k8s/prometheus.md
- k8s/logs-centralized.md
- k8s/extending-api.md
- k8s/operators.md
#- k8s/operators-design.md
-
- shared/thankyou.md

42
slides/5.yml Normal file
View File

@@ -0,0 +1,42 @@
title: |
Jour 5
Opérer Kubernetes
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/enix/formation-highfive-202002)"
gitrepo: github.com/jpetazzo/container.training
slides: http://2020-02-enix.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
-
- k8s/prereqs-admin.md
- k8s/architecture.md
- k8s/deploymentslideshow.md
- k8s/dmuc.md
-
- k8s/multinode.md
- k8s/cni.md
-
- k8s/apilb.md
#- k8s/setup-managed.md
#- k8s/setup-selfhosted.md
- k8s/cluster-upgrade.md
- k8s/cluster-backup.md
- k8s/staticpods.md
-
- k8s/control-plane-auth.md
- k8s/csr-api.md
- k8s/openid-connect.md
- k8s/podsecuritypolicy.md
-
- shared/thankyou.md

View File

@@ -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

View File

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

View File

@@ -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))

View File

@@ -14,6 +14,7 @@ once)
./appendcheck.py $YAML.html
done
fi
zip -qr slides.zip . && echo "Created slides.zip archive."
;;
forever)

View 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.

View File

@@ -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:

View 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](images/kubernetes_pods.svg)
---
## 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.)

View File

@@ -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/).

View File

@@ -1 +0,0 @@
../swarm/links.md

View 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/]

View 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>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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"))
))

View File

@@ -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

View File

@@ -0,0 +1,5 @@
# Exercise -- write a simple pipeline
Let's create a simple pipeline with gitlab
The code is at: https://github.com/enix/kubecoin-build

View File

@@ -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:

View File

@@ -0,0 +1,76 @@
# Advanced Rollout
- In some cases the built-in mechanism of kubernetes is not enough.
- You want more control on the rollout, include a feedback of the monitoring, deploying
on multiple clusters, etc
- Two "main" strategies exist here:
- canary deployment
- blue/green deployment
---
## Canary deployment
- focus on one component of the stack
- deploy a new version of the component close to the production
- redirect some portion of prod traffic to new version
- scale up new version, redirect more traffic, checking everything is ok
- scale down old version
- move component to component with the same procedure
- That's what kubernetes does by default, but does every components at the same time
- Could be paired with `kubectl wait --for` and applying component sequentially,
for hand made canary deployement
---
## Blue/Green deployment
- focus on entire stack
- deploy a new stack
- check the new stack work as espected
- put traffic on new stack, rollback if any goes wrong
- garbage collect the previous infra structure
- there is nothing like that by default in kubernetes
- helm chart with multiple releases is the closest one
- could be paired with ingress feature like `nginx.ingress.kubernetes.io/canary-*`
---
## Not hand-made ?
There is a few additionnal controllers that help achieving those kind of rollout behaviours
They leverage kubernetes API at different levels to achieve this goal.
---
## Spinnaker
- https://www.spinnaker.io
- Help to deploy the same app on multiple cluster.
- Is able to analyse rollout status (canary analysis) and correlate it to monitoring
- Rollback if anything goes wrong
- also support Blue/Green
- Configuration done via UI
---
## Argo-rollout
- https://github.com/argoproj/argo-rollouts
- Replace your deployments with CRD (Custom Resource Definition) "deployment-like"
- Full control via CRDs
- BlueGreen and Canary deployment

View File

@@ -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``` -->
]

View File

@@ -109,7 +109,7 @@ spec:
<!--
```longwait latest: digest: sha256:```
```keys ^C```
```key ^C```
-->
]

View File

@@ -174,7 +174,7 @@ spec:
<!--
```longwait registry:5000/rng-kaniko:latest:```
```keys ^C```
```key ^C```
-->
]

51
slides/k8s/ci-cd.md Normal file
View File

@@ -0,0 +1,51 @@
## Jenkins / Jenkins-X
- Multi-purpose CI
- Self-hosted CI for kubernetes
- create a namespace per commit and apply manifests in the namespace
</br>
"A deploy per feature-branch"
.small[
```shell
curl -L "https://github.com/jenkins-x/jx/releases/download/v2.0.1103/jx-darwin-amd64.tar.gz" | tar xzv jx
./jx boot
```
]
---
## GitLab
- Repository + registry + CI/CD integrated all-in-one
```shell
helm repo add gitlab https://charts.gitlab.io/
helm install gitlab gitlab/gitlab
```
---
## ArgoCD / flux
- Watch a git repository and apply changes to kubernetes
- provide UI to see changes, rollback
.small[
```shell
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
]
---
## Tekton / knative
- knative is serverless project from google
- Tekton leverages knative to run pipelines
- not really user friendly today, but stay tune for wrappers/products

View File

@@ -360,3 +360,7 @@ docker run --rm --net host -v $PWD:/vol \
- [kube-backup](https://github.com/pieterlange/kube-backup)
simple scripts to save resource YAML to a git repository
- [bivac](https://github.com/camptocamp/bivac)
Backup Interface for Volumes Attached to Containers

View File

@@ -154,7 +154,7 @@ class: extra-details
- "Running Kubernetes without nodes"
- Systems like [Virtual Kubelet](https://virtual-kubelet.io/) or Kiyot can run pods using on-demand resources
- Systems like [Virtual Kubelet](https://virtual-kubelet.io/) or [Kiyot](https://static.elotl.co/docs/latest/kiyot/kiyot.html) can run pods using on-demand resources
- Virtual Kubelet can leverage e.g. ACI or Fargate to run pods

View File

@@ -81,7 +81,7 @@
## What version are we running anyway?
- When I say, "I'm running Kubernetes 1.11", is that the version of:
- When I say, "I'm running Kubernetes 1.16", is that the version of:
- kubectl
@@ -139,6 +139,73 @@
---
## Important questions
- Should we upgrade the control plane before or after the kubelets?
- Within the control plane, should we upgrade the API server first or last?
- How often should we upgrade?
- How long are versions maintained?
- All the answers are in [the documentation about version skew policy](https://kubernetes.io/docs/setup/release/version-skew-policy/)!
- Let's review the key elements together ...
---
## Kubernetes uses semantic versioning
- Kubernetes versions look like MAJOR.MINOR.PATCH; e.g. in 1.17.2:
- MAJOR = 1
- MINOR = 17
- PATCH = 2
- It's always possible to mix and match different PATCH releases
(e.g. 1.16.1 and 1.16.6 are compatible)
- It is recommended to run the latest PATCH release
(but it's mandatory only when there is a security advisory)
---
## Version skew
- API server must be more recent than its clients (kubelet and control plane)
- ... Which means it must always be upgraded first
- All components support a difference of one¹ MINOR version
- This allows live upgrades (since we can mix e.g. 1.15 and 1.16)
- It also means that going from 1.14 to 1.16 requires going through 1.15
.footnote[¹Except kubelet, which can be up to two MINOR behind API server,
and kubectl, which can be one MINOR ahead or behind API server.]
---
## Release cycle
- There is a new PATCH relese whenever necessary
(every few weeks, or "ASAP" when there is a security vulnerability)
- There is a new MINOR release every 3 months (approximately)
- At any given time, three MINOR releases are maintained
- ... Which means that MINOR releases are maintained approximately 9 months
- We should expect to upgrade at least every 3 months (on average)
---
## In practice
- We are going to update a few cluster components
@@ -151,47 +218,6 @@
---
## Updating kubelet
- These nodes have been installed using the official Kubernetes packages
- We can therefore use `apt` or `apt-get`
.exercise[
- Log into node `test3`
- View available versions for package `kubelet`:
```bash
apt show kubelet -a | grep ^Version
```
- Upgrade kubelet:
```bash
sudo apt install kubelet=1.15.3-00
```
]
---
## Checking what we've done
.exercise[
- Log into node `test1`
- Check node versions:
```bash
kubectl get nodes -o wide
```
- Create a deployment and scale it to make sure that the node still works
]
---
## Updating the API server
- This cluster has been deployed with kubeadm
@@ -228,7 +254,7 @@
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
```
- Look for the `image:` line, and update it to e.g. `v1.15.0`
- Look for the `image:` line, and update it to e.g. `v1.17.0`
]
@@ -249,9 +275,27 @@
---
## Was that a good idea?
--
**No!**
--
- Remember the guideline we gave earlier:
*To update a component, use whatever was used to install it.*
- This control plane was deployed with kubeadm
- We should use kubeadm to upgrade it!
---
## Updating the whole control plane
- As an example, we'll use kubeadm to upgrade the entire control plane
- Let's make it right, and use kubeadm to upgrade the entire control plane
(note: this is possible only because the cluster was installed with kubeadm)
@@ -264,11 +308,11 @@
]
Note 1: kubeadm thinks that our cluster is running 1.15.0.
Note 1: kubeadm thinks that our cluster is running 1.17.0.
<br/>It is confused by our manual upgrade of the API server!
Note 2: kubeadm itself is still version 1.14.6.
<br/>It doesn't know how to upgrade do 1.15.X.
Note 2: kubeadm itself is still version 1.16.6.
<br/>It doesn't know how to upgrade do 1.17.X.
---
@@ -290,8 +334,8 @@ Note 2: kubeadm itself is still version 1.14.6.
]
Note: kubeadm still thinks that our cluster is running 1.15.0.
<br/>But at least it knows about version 1.15.X now.
Note: kubeadm still thinks that our cluster is running 1.17.0.
<br/>But at least it knows about version 1.17.X now.
---
@@ -307,28 +351,89 @@ Note: kubeadm still thinks that our cluster is running 1.15.0.
- Perform the upgrade:
```bash
sudo kubeadm upgrade apply v1.15.3
sudo kubeadm upgrade apply v1.17.2
```
]
---
## Updating kubelets
## Updating kubelet
- After updating the control plane, we need to update each kubelet
- These nodes have been installed using the official Kubernetes packages
- This requires to run a special command on each node, to download the config
- We can therefore use `apt` or `apt-get`
(this config is generated by kubeadm)
.exercise[
- Log into node `test3`
- View available versions for package `kubelet`:
```bash
apt show kubelet -a | grep ^Version
```
- Upgrade kubelet:
```bash
sudo apt install kubelet=1.17.2-00
```
]
---
## Checking what we've done
.exercise[
- Log into node `test1`
- Check node versions:
```bash
kubectl get nodes -o wide
```
- Create a deployment and scale it to make sure that the node still works
]
---
## Was that a good idea?
--
**Almost!**
--
- Yes, kubelet was installed with distribution packages
- However, kubeadm took care of configuring kubelet
(when doing `kubeadm join ...`)
- We were supposed to run a special command *before* upgrading kubelet!
- That command should be executed on each node
- It will download the kubelet configuration generated by kubeadm
---
## Upgrading kubelet the right way
- The command that we need to run was shown by kubeadm
(after upgrading the control plane)
.exercise[
- Download the configuration on each node, and upgrade kubelet:
```bash
for N in 1 2 3; do
ssh test$N sudo kubeadm upgrade node config --kubelet-version v1.15.3
ssh test$N sudo apt install kubelet=1.15.3-00
ssh test$N sudo kubeadm upgrade node config --kubelet-version v1.17.2
ssh test$N sudo apt install kubelet=1.17.2-00
done
```
]
@@ -337,7 +442,7 @@ Note: kubeadm still thinks that our cluster is running 1.15.0.
## Checking what we've done
- All our nodes should now be updated to version 1.15.3
- All our nodes should now be updated to version 1.17.2
.exercise[
@@ -354,12 +459,12 @@ class: extra-details
## Skipping versions
- This example worked because we went from 1.14 to 1.15
- This example worked because we went from 1.16 to 1.17
- If you are upgrading from e.g. 1.13, you will generally have to go through 1.14 first
- If you are upgrading from e.g. 1.14, you will have to go through 1.15 first
- This means upgrading kubeadm to 1.14.X, then using it to upgrade the cluster
- This means upgrading kubeadm to 1.15.X, then using it to upgrade the cluster
- Then upgrading kubeadm to 1.15.X, etc.
- Then upgrading kubeadm to 1.16.X, etc.
- **Make sure to read the release notes before upgrading!**

View File

@@ -28,7 +28,7 @@ The reference plugins are available [here].
Look in each plugin's directory for its documentation.
[here]: https://github.com/containernetworking/plugins/tree/master/plugins
[here]: https://github.com/containernetworking/plugins
---

View File

@@ -10,6 +10,29 @@
---
## What can we do with Kubernetes?
- Let's imagine that we have a 3-tier e-commerce app:
- web frontend
- API backend
- database (that we will keep out of Kubernetes for now)
- We have built images for our frontend and backend components
(e.g. with Dockerfiles and `docker build`)
- We are running them successfully with a local environment
(e.g. with Docker Compose)
- Let's see how we would deploy our app on Kubernetes!
---
## Basic things we can ask Kubernetes to do
--
@@ -199,6 +222,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!
@@ -299,6 +346,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)

View File

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

View File

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

View File

@@ -52,7 +52,7 @@
<!-- ##VERSION## -->
- Unfortunately, as of Kubernetes 1.15, the CLI cannot create daemon sets
- Unfortunately, as of Kubernetes 1.17, the CLI cannot create daemon sets
--
@@ -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:``` -->
]
--
@@ -425,7 +427,7 @@ class: extra-details
- We need to change the selector of the `rng` service!
- Let's add another label to that selector (e.g. `enabled=yes`)
- Let's add another label to that selector (e.g. `active=yes`)
---
@@ -443,11 +445,11 @@ class: extra-details
## The plan
1. Add the label `enabled=yes` to all our `rng` pods
1. Add the label `active=yes` to all our `rng` pods
2. Update the selector for the `rng` service to also include `enabled=yes`
2. Update the selector for the `rng` service to also include `active=yes`
3. Toggle traffic to a pod by manually adding/removing the `enabled` label
3. Toggle traffic to a pod by manually adding/removing the `active` label
4. Profit!
@@ -462,7 +464,7 @@ be any interruption.*
## Adding labels to pods
- We want to add the label `enabled=yes` to all pods that have `app=rng`
- We want to add the label `active=yes` to all pods that have `app=rng`
- We could edit each pod one by one with `kubectl edit` ...
@@ -472,9 +474,9 @@ be any interruption.*
.exercise[
- Add `enabled=yes` to all pods that have `app=rng`:
- Add `active=yes` to all pods that have `app=rng`:
```bash
kubectl label pods -l app=rng enabled=yes
kubectl label pods -l app=rng active=yes
```
]
@@ -493,7 +495,7 @@ be any interruption.*
.exercise[
- Update the service to add `enabled: yes` to its selector:
- Update the service to add `active: yes` to its selector:
```bash
kubectl edit service rng
```
@@ -501,11 +503,11 @@ be any interruption.*
<!--
```wait Please edit the object below```
```keys /app: rng```
```keys ^J```
```keys noenabled: yes```
```keys ^[``` ]
```key ^J```
```keys noactive: yes```
```key ^[``` ]
```keys :wq```
```keys ^J```
```key ^J```
-->
]
@@ -528,7 +530,7 @@ be any interruption.*
- If we want the string `"42"` or the string `"yes"`, we have to quote them
- So we have to use `enabled: "yes"`
- So we have to use `active: "yes"`
.footnote[For a good laugh: if we had used "ja", "oui", "si" ... as the value, it would have worked!]
@@ -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 `active: "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```
-->
]
@@ -565,7 +566,7 @@ If we did everything correctly, the web UI shouldn't show any change.
- We want to disable the pod that was created by the deployment
- All we have to do, is remove the `enabled` label from that pod
- All we have to do, is remove the `active` label from that pod
- To identify that pod, we can use its name
@@ -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-
kubectl label pod -l app=rng,pod-template-hash active-
```
(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
@@ -613,7 +623,7 @@ class: extra-details
- If we scale up our cluster by adding new nodes, the daemon set will create more pods
- These pods won't have the `enabled=yes` label
- These pods won't have the `active=yes` label
- If we want these pods to have that label, we need to edit the daemon set spec

View File

@@ -0,0 +1,10 @@
## We are done, what else ?
We have seen what means developping an application on kubernetes.
There still few subjects to tackle that are not purely relevant for developers
They have *some involvement* for developers:
- Monitoring
- Security

View File

@@ -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.

View File

@@ -0,0 +1,5 @@
## Exercise - building with Kubernetes
- Let's go to https://github.com/enix/kubecoin
- Our goal is to follow the instructions and complete exercise #1

View File

@@ -0,0 +1,3 @@
## Exercice - build with kaniko
Complete exercise #2, (again code at: https://github.com/enix/kubecoin )

View File

@@ -0,0 +1,5 @@
## Exercice - monitor with opentelemetry
Complete exercise #5, (again code at: https://github.com/enix/kubecoin )
*Note: Not all daemon are "ready" for opentelemetry, only `rng` and `worker`

View File

@@ -0,0 +1,5 @@
## Exercice - monitor with prometheus
Complete exercise #4, (again code at: https://github.com/enix/kubecoin )
*Note: Not all daemon are "ready" for prometheus, only `hasher` and `redis`

View File

@@ -8,6 +8,8 @@ We are going to cover:
- Admission Webhooks
- The Aggregation Layer
---
## Revisiting the API server
@@ -46,6 +48,90 @@ We are going to cover:
---
## A very simple CRD
The YAML below describes a very simple CRD representing different kinds of coffee:
```yaml
apiVersion: apiextensions.k8s.io/v1alpha1
kind: CustomResourceDefinition
metadata:
name: coffees.container.training
spec:
group: container.training
version: v1alpha1
scope: Namespaced
names:
plural: coffees
singular: coffee
kind: Coffee
shortNames:
- cof
```
---
## Creating a CRD
- Let's create the Custom Resource Definition for our Coffee resource
.exercise[
- Load the CRD:
```bash
kubectl apply -f ~/container.training/k8s/coffee-1.yaml
```
- Confirm that it shows up:
```bash
kubectl get crds
```
]
---
## Creating custom resources
The YAML below defines a resource using the CRD that we just created:
```yaml
kind: Coffee
apiVersion: container.training/v1alpha1
metadata:
name: arabica
spec:
taste: strong
```
.exercise[
- Create a few types of coffee beans:
```bash
kubectl apply -f ~/container.training/k8s/coffees.yaml
```
]
---
## Viewing custom resources
- By default, `kubectl get` only shows name and age of custom resources
.exercise[
- View the coffee beans that we just created:
```bash
kubectl get coffees
```
]
- We can improve that, but it's outside the scope of this section!
---
## What can we do with CRDs?
There are many possibilities!
@@ -65,7 +151,7 @@ There are many possibilities!
- Replacing built-in types with CRDs
(see [this lightning talk by Tim Hockin](https://www.youtube.com/watch?v=ji0FWzFwNhA&index=2&list=PLj6h78yzYM2PZf9eA7bhWnIh_mK1vyOfU))
(see [this lightning talk by Tim Hockin](https://www.youtube.com/watch?v=ji0FWzFwNhA))
---
@@ -81,7 +167,7 @@ There are many possibilities!
- Generally, when creating a CRD, we also want to run a *controller*
(otherwise nothing will happen when we create resources of that type)
(otherwise nothing will happen when we create resources of that type)
- The controller will typically *watch* our custom resources
@@ -95,6 +181,22 @@ Examples:
---
## (Ab)using the API server
- If we need to store something "safely" (as in: in etcd), we can use CRDs
- This gives us primitives to read/write/list objects (and optionally validate them)
- The Kubernetes API server can run on its own
(without the scheduler, controller manager, and kubelets)
- By loading CRDs, we can have it manage totally different objects
(unrelated to containers, clusters, etc.)
---
## Service catalog
- *Service catalog* is another extension mechanism
@@ -109,7 +211,7 @@ Examples:
- ClusterServiceClass
- ClusterServicePlan
- ServiceInstance
- ServiceBinding
- ServiceBinding
- It uses the Open service broker API
@@ -117,17 +219,13 @@ Examples:
## Admission controllers
- When a Pod is created, it is associated with a ServiceAccount
- Admission controllers are another way to extend the Kubernetes API
(even if we did not specify one explicitly)
- Instead of creating new types, admission controllers can transform or vet API requests
- That ServiceAccount was added on the fly by an *admission controller*
- The diagram on the next slide shows the path of an API request
(specifically, a *mutating admission controller*)
- Admission controllers sit on the API request path
(see the cool diagram on next slide, courtesy of Banzai Cloud)
(courtesy of Banzai Cloud)
---
@@ -137,7 +235,7 @@ class: pic
---
## Admission controllers
## Types of admission controllers
- *Validating* admission controllers can accept/reject the API call
@@ -151,7 +249,27 @@ class: pic
(see [documentation](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do) for a list)
- But we can also define our own!
- We can also dynamically define and register our own
---
class: extra-details
## Some built-in admission controllers
- ServiceAccount:
automatically adds a ServiceAccount to Pods that don't explicitly specify one
- LimitRanger:
applies resource constraints specified by LimitRange objects when Pods are created
- NamespaceAutoProvision:
automatically creates namespaces when an object is created in a non-existent namespace
*Note: #1 and #2 are enabled by default; #3 is not.*
---
@@ -191,19 +309,25 @@ class: pic
---
## (Ab)using the API server
## The aggregation layer
- If we need to store something "safely" (as in: in etcd), we can use CRDs
- We can delegate entire parts of the Kubernetes API to external servers
- This gives us primitives to read/write/list objects (and optionally validate them)
- This is done by creating APIService resources
- The Kubernetes API server can run on its own
(check them with `kubectl get apiservices`!)
(without the scheduler, controller manager, and kubelets)
- The APIService resource maps a type (kind) and version to an external service
- By loading CRDs, we can have it manage totally different objects
- All requests concerning that type are sent (proxied) to the external service
(unrelated to containers, clusters, etc.)
- This allows to have resources like CRDs, but that aren't stored in etcd
- Example: `metrics-server`
(storing live metrics in etcd would be extremely inefficient)
- Requires significantly more work than CRDs!
---
@@ -218,3 +342,5 @@ class: pic
- [Built-in Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)
- [Dynamic Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)
- [Aggregation Layer](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/)

View 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`

View 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
```
]

View 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
View 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
kubectl 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
View 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
kubectl 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

View File

@@ -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
```
]

View File

@@ -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```
-->
]

View File

@@ -120,19 +120,13 @@
- We want our ingress load balancer to be available on port 80
- We could do that with a `LoadBalancer` service
- The best way to do that would be with a `LoadBalancer` service
... but it requires support from the underlying infrastructure
- We could use pods specifying `hostPort: 80`
- Instead, we are going to use the `hostNetwork` mode on the Traefik pods
... but with most CNI plugins, this [doesn't work or requires additional setup](https://github.com/kubernetes/kubernetes/issues/23920)
- We could use a `NodePort` service
... but that requires [changing the `--service-node-port-range` flag in the API server](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/)
- Last resort: the `hostNetwork` mode
- Let's see what this `hostNetwork` mode is about ...
---
@@ -170,6 +164,26 @@
---
class: extra-details
## Other techniques to expose port 80
- We could use pods specifying `hostPort: 80`
... but with most CNI plugins, this [doesn't work or requires additional setup](https://github.com/kubernetes/kubernetes/issues/23920)
- We could use a `NodePort` service
... but that requires [changing the `--service-node-port-range` flag in the API server](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/)
- We could create a service with an external IP
... this would work, but would require a few extra steps
(figuring out the IP address and adding it to the service)
---
## Running Traefik
- The [Traefik documentation](https://docs.traefik.io/user-guide/kubernetes/#deploy-trfik-using-a-deployment-or-daemonset) tells us to pick between Deployment and Daemon Set
@@ -524,3 +538,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).

34
slides/k8s/kaniko.md Normal file
View File

@@ -0,0 +1,34 @@
## Privileged container
- Running privileged container could be really harmful for the node it run on.
- Getting control of a node could expose other containers in the cluster and the cluster itself
- It's even worse when it is docker that run in this privileged container
- `docker build` doesn't allow to run privileged container for building layer
- nothing forbid to run `docker run --privileged`
---
## Kaniko
- https://github.com/GoogleContainerTools/kaniko
- *kaniko doesn't depend on a Docker daemon and executes each command
within a Dockerfile completely in userspace*
- Kaniko is only a build system, there is no runtime like docker does
- generates OCI compatible image, so could be run on Docker or other CRI
- use a different cache system than Docker
---
## Rootless docker and rootless buildkit
- This is experimental
- Have a lot of requirement of kernel param, options to set
- But it exists

View File

@@ -1,55 +1,131 @@
# Exposing containers
- `kubectl expose` creates a *service* for existing pods
- We can connect to our pods using their IP address
- A *service* is a stable address for a pod (or a bunch of pods)
- Then we need to figure out a lot of things:
- If we want to connect to our pod(s), we need to create a *service*
- how do we look up the IP address of the pod(s)?
- Once a service is created, CoreDNS will allow us to resolve it by name
- how do we connect from outside the cluster?
(i.e. after creating service `hello`, the name `hello` will resolve to something)
- how do we load balance traffic?
- There are different types of services, detailed on the following slides:
- what if a pod fails?
- Kubernetes has a resource type named *Service*
- Services address all these questions!
---
## Services in a nutshell
- Services give us a *stable endpoint* to connect to a pod or a group of pods
- An easy way to create a service is to use `kubectl expose`
- If we have a deployment named `my-little-deploy`, we can run:
`kubectl expose deployment my-little-deploy --port=80`
... and this will create a service with the same name (`my-little-deploy`)
- Services are automatically added to an internal DNS zone
(in the example above, our code can now connect to http://my-little-deploy/)
---
## Advantages of services
- We don't need to look up the IP address of the pod(s)
(we resolve the IP address of the service using DNS)
- There are multiple service types; some of them allow external traffic
(e.g. `LoadBalancer` and `NodePort`)
- Services provide load balancing
(for both internal and external traffic)
- Service addresses are independent from pods' addresses
(when a pod fails, the service seamlessly sends traffic to its replacement)
---
## Many kinds and flavors of service
- There are different types of services:
`ClusterIP`, `NodePort`, `LoadBalancer`, `ExternalName`
---
- There are also *headless services*
## Basic service types
- Services can also have optional *external IPs*
- `ClusterIP` (default type)
- There is also another resource type called *Ingress*
- 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
(specifically for HTTP services)
- `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.
- Wow, that's a lot! Let's start with the basics ...
---
## 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)
---
@@ -86,7 +162,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
@@ -134,9 +213,7 @@ Under the hood: `kube-proxy` is using a userland proxy and a bunch of `iptables`
- As a result: you *have to* indicate the port number for your service
- Running services with arbitrary port (or port ranges) requires hacks
(e.g. host networking mode)
(with some exceptions, like `ExternalName` or headless services, covered later)
---
@@ -153,6 +230,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:
@@ -175,7 +254,48 @@ Try it a few times! Our requests are load balanced across multiple pods.
class: extra-details
## If we don't need a load balancer
## `ExternalName`
- Services of type `ExternalName` are quite different
- 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`*
---
class: extra-details
## External IPs
- We can add an External IP to a service, e.g.:
```bash
kubectl expose deploy my-little-deploy --port=80 --external-ip=1.2.3.4
```
- `1.2.3.4` should be the address of one of our nodes
(it could also be a virtual address, service address, or VIP, shared by multiple nodes)
- Connections to `1.2.3.4:80` will be sent to our service
- External IPs will also show up on services of type `LoadBalancer`
(they will be added automatically by the process provisioning the load balancer)
---
class: extra-details
## Headless services
- Sometimes, we want to access our scaled services directly:
@@ -195,7 +315,7 @@ class: extra-details
class: extra-details
## Headless services
## Creating a headless services
- A headless service is obtained by setting the `clusterIP` field to `None`
@@ -279,18 +399,42 @@ 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
## The DNS zone
- If we want to accept external traffic, we can use one of these:
- In the `kube-system` namespace, there should be a service named `kube-dns`
- NodePort (expose a service on a TCP port between 30000-32768)
- This is the internal DNS server that can resolve service names
- LoadBalancer (provision a cloud load balancer for our service)
- The default domain name for the service we created is `default.svc.cluster.local`
- ExternalIP (use one node's external IP address)
.exercise[
- Ingress (a special mechanism for HTTP services)
- Get the IP address of the internal DNS server:
```bash
IP=$(kubectl -n kube-system get svc kube-dns -o jsonpath={.spec.clusterIP})
```
*We'll see NodePorts and Ingresses more in detail later.*
- Resolve the cluster IP for the `httpenv` service:
```bash
host httpenv.default.svc.cluster.local $IP
```
]
---
class: extra-details
## `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

View File

@@ -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/...)

View File

@@ -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:

View File

@@ -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``` -->
@@ -155,6 +154,11 @@ pod/pingpong-7c8bbcd9bc-6c9qz 1/1 Running 0 10m
- Leave that command running, so that we can keep an eye on these logs
<!--
```wait seq=3```
```tmux split-pane -h```
-->
]
---
@@ -207,11 +211,21 @@ We could! But the *deployment* would notice it right away, and scale back to the
- 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.
@@ -236,10 +250,30 @@ Let's leave `kubectl logs` running while we keep exploring.
watch kubectl get pods
```
<!--
```wait Every 2.0s```
```tmux split-pane -v```
-->
- 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```
-->
]
---
@@ -308,7 +342,8 @@ Let's leave `kubectl logs` running while we keep exploring.
- Create the Cron Job:
```bash
kubectl run --schedule="*/3 * * * *" --restart=OnFailure --image=alpine sleep 10
kubectl run every3mins --schedule="*/3 * * * *" --restart=OnFailure \
--image=alpine sleep 10
```
- Check the resource that was created:
@@ -367,12 +402,12 @@ Let's leave `kubectl logs` running while we keep exploring.
## 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
@@ -419,7 +454,7 @@ Let's leave `kubectl logs` running while we keep exploring.
<!--
```wait seq=```
```keys ^C```
```key ^C```
-->
]
@@ -448,6 +483,8 @@ class: extra-details
kubectl logs -l run=pingpong --tail 1 -f
```
<!-- ```wait error:``` -->
]
We see a message like the following one:
@@ -516,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)

View File

@@ -12,9 +12,9 @@
<!--
```wait RESTARTS```
```keys ^C```
```key ^C```
```wait AVAILABLE```
```keys ^C```
```key ^C```
-->
- Now, create more `worker` replicas:

View File

@@ -0,0 +1,78 @@
# Security and kubernetes
There are many mechanisms in kubernetes to ensure the security.
Obviously the more you constrain your app, the better.
There is also mechanism to forbid "unsafe" application to be launched on
kubernetes, but that's more for ops-guys 😈 (more on that next days)
Let's focus on what can we do on the developer latop, to make app
compatible with secure system, enforced or not (it's always a good practice)
---
## No container in privileged mode
- risks:
- If one privileged container get compromised,
we basically get full access to the node from within a container
(not need to tamper auth logs, alter binary).
- Sniffing networks allow often to get access to the entire cluster.
- how to avoid:
```
[...]
spec:
containers:
- name: foo
securityContext:
privileged: false
```
Luckily that's the default !
---
## No container run as "root"
- risks:
- bind mounting a directory like /usr/bin allow to change node system core
</br>ex: copy a tampered version of "ping", wait for an admin to login
and to issue a ping command and bingo !
- how to avoid:
```
[...]
spec:
containers:
- name: foo
securityContext:
runAsUser: 1000
runAsGroup: 100
```
- The default is to use the image default
- If your writing your own Dockerfile, don't forget about the `USER` instruction
---
## Capabilities
- You can give capabilities one-by-one to a container
- It's useful if you need more capabilities (for some reason), but not grating 'root' privileged
- risks: no risks whatsoever, except by granting a big list of capabilities
- how to use:
```
[...]
spec:
containers:
- name: foo
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME"]
drop: []
```
The default use the container runtime defaults
- and we can also drop default capabilities granted by the container runtime !

View File

@@ -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.

View File

@@ -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
```
]

View File

@@ -84,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```
-->
]
@@ -117,7 +117,7 @@ Exactly what we need!
<!--
```wait weave-npc```
```keys ^C```
```key ^C```
-->
]
@@ -138,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```
-->
]

View File

@@ -102,8 +102,6 @@
]
- Some tools like Helm will create namespaces automatically when needed
---
## Using namespaces
@@ -341,12 +339,29 @@ Note: we could have used `--namespace=default` for the same result.
- `kube-ps1` makes it easy to track these, by showing them in our shell prompt
- It's a simple shell script available from https://github.com/jonmosco/kube-ps1
- It is installed on our training clusters, and when using [shpod](https://github.com/jpetazzo/shpod)
- On our clusters, `kube-ps1` is installed and included in `PS1`:
- It gives us a prompt looking like this one:
```
[123.45.67.89] `(kubernetes-admin@kubernetes:default)` docker@node1 ~
```
(The highlighted part is `context:namespace`, managed by `kube-ps1`)
- Highly recommended if you work across multiple contexts or namespaces!
---
## Installing `kube-ps1`
- It's a simple shell script available from https://github.com/jonmosco/kube-ps1
- It needs to be [installed in our profile/rc files](https://github.com/jonmosco/kube-ps1#installing)
(instructions differ depending on platform, shell, etc.)
- Once installed, it defines aliases called `kube_ps1`, `kubeon`, `kubeoff`
(to selectively enable/disable it when needed)
- Pro-tip: install it on your machine during the next break!

View File

@@ -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.

179
slides/k8s/on-desktop.md Normal file
View File

@@ -0,0 +1,179 @@
# Development Workflow
In this section we will see how to set up a local development workflow.
We will list multiple options.
Keep in mind that we don't have to use *all* these tools!
It's up to the developer to find what best suits them.
---
## What does it mean to develop on Kubernetes ?
In theory, the generic workflow is:
1. Make changes to our code or edit a Dockerfile
2. Build a new Docker image with a new tag
3. Push that Docker image to a registry
4. Update the YAML or templates referencing that Docker image
<br/>(e.g. of the corresponding Deployment, StatefulSet, Job ...)
5. Apply the YAML or templates
6. Are we satisfied with the result?
<br/>No → go back to step 1 (or step 4 if the image is OK)
<br/>Yes → commit and push our changes to source control
---
## A few quirks
In practice, there are some details that make this workflow more complex.
- We need a Docker container registry to store our images
<br/>
(for Open Source projects, a free Docker Hub account works fine)
- We need to set image tags properly, hopefully automatically
- If we decide to use a fixed tag (like `:latest`) instead:
- we need to specify `imagePullPolicy=Always` to force image pull
- we need to trigger a rollout when we want to deploy a new image
<br/>(with `kubectl rollout restart` or by killing the running pods)
- We need a fast internet connection to push the images
- We need to regularly clean up the registry to avoid accumulating old images
---
## When developing locally
- If we work with a local cluster, pushes and pulls are much faster
- Even better, with a one-node cluster, most of these problems disappear
- If we build and run the images on the same node, ...
- we don't need to push images
- we don't need a fast internet connection
- we don't need a registry
- we can use bind mounts to edit code locally and make changes available immediately in running containers
- This means that it is much simpler to deploy to local development environment (like Minikube, Docker Desktop ...) than to a "real" cluster
---
## Minikube
- Start a VM with the hypervisor of your choice: VirtualBox, kvm, Hyper-V ...
- Well supported by the Kubernetes community
- Lot of addons
- Easy cleanup: delete the VM with `minikube delete`
- Bind mounts depend on the underlying hypervisor
(they may require additionnal setup)
---
## Docker Desktop
- Available for Mac and Windows
- Start a VM with the appropriate hypervisor (even better!)
- Bind mounts work out of the box
```yaml
volumes:
- name: repo_dir
hostPath:
path: /C/Users/Enix/my_code_repository
```
- Ingress and other addons need to be installed manually
---
## Kind
- Kubernetes-in-Docker
- Uses Docker-in-Docker to run Kubernetes
<br/>
(technically, it's more like Containerd-in-Docker)
- We don't get a real Docker Engine (and cannot build Dockerfiles)
- Single-node by default, but multi-node clusters are possible
- Very convenient to test Kubernetes deployments when only Docker is available
<br/>
(e.g. on public CI services like Travis, Circle, GitHub Actions ...)
- Bind mounts require extra configuration
- Extra configuration for a couple of addons, totally custom for other
- Doesn't work with BTRFS (sorry BTRFS users😢)
---
## microk8s
- Distribution of Kubernetes using Snap
(Snap is a container-like method to install software)
- Available on Ubuntu and derivatives
- Bind mounts work natively (but require extra setup if we run in a VM)
- Big list of addons; easy to install
---
## Proper tooling
The simple workflow seems to be:
- set up a one-node cluster with one of the methods mentioned previously,
- find the remote Docker endpoint,
- configure the `DOCKER_HOST` variable to use that endpoint,
- follow the previous 7-step workflow.
Can we do better?
---
## Helpers
- Skaffold (https://skaffold.dev/):
- build with docker, kaniko, google builder
- install with pure yaml manifests, kustomize, helm
- Tilt (https://tilt.dev/)
- Tiltfile is programmatic format (python ?)
- Primitive for building with docker
- Primitive for deploying with pure yaml manifests, kustomize, helm
- Garden (https://garden.io/)
- Forge (https://forge.sh/)

View File

@@ -0,0 +1,84 @@
# OpenTelemetry
*OpenTelemetry* is a "tracing" framework.
It's a fusion of two other frameworks:
*OpenTracing* and *OpenCensus*.
Its goal is to provide deep integration with programming languages and
application frameworks to enabled deep dive tracing of different events accross different components.
---
## Span ! span ! span !
- A unit of tracing is called a *span*
- A span has: a start time, a stop time, and an ID
- It represents an action that took some time to complete
(e.g.: function call, database transaction, REST API call ...)
- A span can have a parent span, and can have multiple child spans
(e.g.: when calling function `B`, sub-calls to `C` and `D` were issued)
- Think of it as a "tree" of calls
---
## Distributed tracing
- When two components interact, their spans can be connected together
- Example: microservice `A` sends a REST API call to microservice `B`
- `A` will have a span for the call to `B`
- `B` will have a span for the call from `A`
<br/>(that normally starts shortly after, and finishes shortly before)
- the span of `A` will be the parent of the span of `B`
- they join the same "tree" of calls
<!-- FIXME the thing below? -->
details: `A` will send headers (depends of the protocol used) to tag the span ID,
so that `B` can generate child span and joining the same tree of call
---
## Centrally stored
- What do we do with all these spans?
- We store them!
- In the previous exemple:
- `A` will send trace information to its local agent
- `B` will do the same
- every span will end up in the same DB
- at a later point, we can reconstruct the "tree" of call and analyze it
- There are multiple implementations of this stack (agent + DB + web UI)
(the most famous open source ones are Zipkin and Jaeger)
---
## Data sampling
- Do we store *all* the spans?
(it looks like this could need a lot of storage!)
- No, we can use *sampling*, to reduce storage and network requirements
- Smart sampling is applied directly in the application to save CPU if span is not needed
- It also insures that if a span is marked as sampled, all child span are sampled as well
(so that the tree of call is complete)

View File

@@ -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
kubectl 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?

View File

@@ -108,7 +108,7 @@ kubectl wait deploy/worker --for condition=available
<!--
```wait units of work done, updating hash counter```
```keys ^C```
```key ^C```
-->
]

View File

@@ -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
```
]

View File

@@ -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``` -->
]

View File

@@ -0,0 +1,150 @@
# Prometheus
Prometheus is a monitoring system with a small storage I/O footprint.
It's quite ubiquitous in the Kubernetes world.
This section is not an in-depth description of Prometheus.
*Note: More on Prometheus next day!*
<!--
FIXME maybe just use prometheus.md and add this file after it?
This way there is not need to write a Prom intro.
-->
---
## Prometheus exporter
- Prometheus *scrapes* (pulls) metrics from *exporters*
- A Prometheus exporter is an HTTP endpoint serving a response like this one:
```
# HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="post",code="200"} 1027 1395066363000
http_requests_total{method="post",code="400"} 3 1395066363000
# Minimalistic line:
metric_without_timestamp_and_labels 12.47
```
- Our goal, as a developer, will be to expose such an endpoint to Prometheus
---
## Implementing a Prometheus exporter
Multiple strategies can be used:
- Implement the exporter in the application itself
(especially if it's already an HTTP server)
- Use building blocks that may already expose such an endpoint
(puma, uwsgi)
- Add a sidecar exporter that leverages and adapts an existing monitoring channel
(e.g. JMX for Java applications)
---
## Implementing a Prometheus exporter
- The Prometheus client libraries are often the easiest solution
- They offer multiple ways of integration, including:
- "I'm already running a web server, just add a monitoring route"
- "I don't have a web server (or I want another one), please run one in a thread"
- Client libraries for various languages:
- https://github.com/prometheus/client_python
- https://github.com/prometheus/client_ruby
- https://github.com/prometheus/client_golang
(Can you see the pattern?)
---
## Adding a sidecar exporter
- There are many exporters available already:
https://prometheus.io/docs/instrumenting/exporters/
- These are "translators" from one monitoring channel to another
- Writing your own is not complicated
(using the client libraries mentioned previously)
- Avoid exposing the internal monitoring channel more than enough
(the app and its sidecars run in the same network namespace,
<br/>so they can communicate over `localhost`)
---
## Configuring the Prometheus server
- We need to tell the Prometheus server to *scrape* our exporter
- Prometheus has a very flexible "service discovery" mechanism
(to discover and enumerate the targets that it should scrape)
- Depending on how we installed Prometheus, various methods might be available
---
## Configuring Prometheus, option 1
- Edit `prometheus.conf`
- Always possible
(we should always have a Prometheus configuration file somewhere!)
- Dangerous and error-prone
(if we get it wrong, it is very easy to break Prometheus)
- Hard to maintain
(the file will grow over time, and might accumulate obsolete information)
---
## Configuring Prometheus, option 2
- Add *annotations* to the pods or services to monitor
- We can do that if Prometheus is installed with the official Helm chart
- Prometheus will detect these annotations and automatically start scraping
- Example:
```yaml
annotations:
prometheus.io/port: 9090
prometheus.io/path: /metrics
```
---
## Configuring Prometheus, option 3
- Create a ServiceMonitor custom resource
- We can do that if we are using the CoreOS Prometheus operator
- See the [Prometheus operator documentation](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#servicemonitor) for more details

View File

@@ -204,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

99
slides/k8s/registries.md Normal file
View File

@@ -0,0 +1,99 @@
# Registries
- There are lots of options to ship our container images to a registry
- We can group them depending on some characteristics:
- SaaS or self-hosted
- with or without a build system
---
## Docker registry
- Self-hosted and [open source](https://github.com/docker/distribution)
- Runs in a single Docker container
- Supports multiple storage backends
- Supports basic authentication out of the box
- [Other authentication schemes](https://docs.docker.com/registry/deploying/#more-advanced-authentication) through proxy or delegation
- No build system
- To run it with the Docker engine:
```shell
docker run -d -p 5000:5000 --name registry registry:2
```
- Or use the dedicated plugin in minikube, microk8s, etc.
---
## Harbor
- Self-hostend and [open source](https://github.com/goharbor/harbor)
- Supports both Docker images and Helm charts
- Advanced authentification mechanism
- Multi-site synchronisation
- Vulnerability scanning
- No build system
- To run it with Helm:
```shell
helm repo add harbor https://helm.goharbor.io
helm install my-release harbor/harbor
```
---
## Gitlab
- Available both as a SaaS product and self-hosted
- SaaS product is free for open source projects; paid subscription otherwise
- Some parts are [open source](https://gitlab.com/gitlab-org/gitlab-foss/)
- Integrated CI
- No build system (but a custom build system can be hooked to the CI)
- To run it with Helm:
```shell
helm repo add gitlab https://charts.gitlab.io/
helm install gitlab gitlab/gitlab
```
---
## Docker Hub
- SaaS product: [hub.docker.com](https://hub.docker.com)
- Free for public image; paid subscription for private ones
- Build system included
---
## Quay
- Available both as a SaaS product (Quay) and self-hosted ([quay.io](https://quay.io))
- SaaS product is free for public repositories; paid subscription otherwise
- Some components of Quay and quay.io are open source
(see [Project Quay](https://www.projectquay.io/) and the [announcement](https://www.redhat.com/en/blog/red-hat-introduces-open-source-project-quay-container-registry))
- Build system included

View File

@@ -80,6 +80,7 @@
- Rolling updates can be monitored with the `kubectl rollout` subcommand
---
class: hide-exercise
## Rolling out the new `worker` service
@@ -94,7 +95,7 @@
<!--
```wait NAME```
```keys ^C```
```key ^C```
-->
- Update `worker` either with `kubectl edit`, or by running:
@@ -109,6 +110,7 @@
That rollout should be pretty quick. What shows in the web UI?
---
class: hide-exercise
## Give it some time
@@ -131,6 +133,7 @@ That rollout should be pretty quick. What shows in the web UI?
(The grace period is 30 seconds, but [can be changed](https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods) if needed)
---
class: hide-exercise
## Rolling out something invalid
@@ -148,10 +151,10 @@ That rollout should be pretty quick. What shows in the web UI?
kubectl rollout status deploy worker
```
<!--
/<!--
```wait Waiting for deployment```
```keys ^C```
-->
```key ^C```
/-->
]
@@ -162,6 +165,7 @@ Our rollout is stuck. However, the app is not dead.
(After a minute, it will stabilize to be 20-25% slower.)
---
class: hide-exercise
## What's going on with our rollout?
@@ -202,6 +206,7 @@ class: extra-details
- Our rollout is stuck at this point!
---
class: hide-exercise
## Checking the dashboard during the bad rollout
@@ -218,6 +223,7 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
]
---
class: hide-exercise
## Recovering from a bad rollout
@@ -229,11 +235,7 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
.exercise[
<!--
```keys
^C
```
-->
<!-- ```key ^C``` -->
- Cancel the deployment and wait for the dust to settle:
```bash
@@ -244,6 +246,7 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
]
---
class: hide-exercise
## Rolling back to an older version
@@ -254,6 +257,7 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
- How can we get back to the previous version?
---
class: hide-exercise
## Multiple "undos"
@@ -273,6 +277,7 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
🤔 That didn't work.
---
class: hide-exercise
## Multiple "undos" don't work
@@ -295,6 +300,8 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
---
class: hide-exercise
## In this specific scenario
- Our version numbers are easy to guess
@@ -305,6 +312,8 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
---
class: hide-exercise
## Listing versions
- We can list successive versions of a Deployment with `kubectl rollout history`
@@ -325,6 +334,7 @@ We might see something like 1, 4, 5.
(Depending on how many "undos" we did before.)
---
class: hide-exercise
## Explaining deployment revisions
@@ -336,7 +346,7 @@ We might see something like 1, 4, 5.
- Check the annotations for our replica sets:
```bash
kubectl describe replicasets -l app=worker | grep -A3
kubectl describe replicasets -l app=worker | grep -A3 ^Annotations
```
]
@@ -344,6 +354,7 @@ We might see something like 1, 4, 5.
---
class: extra-details
class: hide-exercise
## What about the missing revisions?
@@ -358,6 +369,7 @@ class: extra-details
(if we wanted to!)
---
class: hide-exercise
## Rolling back to an older version
@@ -377,6 +389,7 @@ class: extra-details
---
class: extra-details
class: hide-exercise
## Changing rollout parameters
@@ -384,7 +397,7 @@ class: extra-details
- revert to `v0.1`
- be conservative on availability (always have desired number of available workers)
- go slow on rollout speed (update only one pod at a time)
- go slow on rollout speed (update only one pod at a time)
- give some time to our workers to "warm up" before starting more
The corresponding changes can be expressed in the following YAML snippet:
@@ -408,6 +421,7 @@ spec:
---
class: extra-details
class: hide-exercise
## Applying changes through a YAML patch
@@ -438,6 +452,6 @@ class: extra-details
kubectl get deploy -o json worker |
jq "{name:.metadata.name} + .spec.strategy.rollingUpdate"
```
]
]
]

View File

@@ -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```
-->
]
--

View File

@@ -0,0 +1,72 @@
# sealed-secrets
- https://github.com/bitnami-labs/sealed-secrets
- has a server side (standard kubernetes deployment) and a client side *kubeseal* binary
- server-side start by generating a key pair, keep the private, expose the public.
- To create a sealed-secret, you only need access to public key
- You can enforce access with RBAC rules of kubernetes
---
## sealed-secrets how to
- adding a secret: *kubeseal* will cipher it with the public key
- server side controller will re-create original secret, when the ciphered one are added to the cluster
- it makes it "safe" to add those secret to your source tree
- since version 0.9 key rotation are enable by default, so remember to backup private keys regularly.
</br> (or you won't be able to decrypt all you keys, in a case of *disaster recovery*)
---
## First "sealed-secret"
.exercise[
- Install *kubeseal*
```bash
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.9.7/kubeseal-linux-amd64 -O kubeseal
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
```
- Install controller
```bash
helm install -n kube-system sealed-secrets-controller stable/sealed-secrets
```
- Create a secret you don't want to leak
```bash
kubectl create secret generic --from-literal=foo=bar my-secret -o yaml --dry-run \
| kubeseal > mysecret.yaml
kubectl apply -f mysecret.yaml
```
]
---
## Alternative: sops / git crypt
- You can work a VCS level (ie totally abstracted from kubernetess)
- sops (https://github.com/mozilla/sops), VCS agnostic, encrypt portion of files
- git-crypt that work with git to transparently encrypt (some) files in git
---
## Other alternative
- You can delegate secret management to another component like *hashicorp vault*
- Can work in multiple ways:
- encrypt secret from API-server (instead of the much secure *base64*)
- encrypt secret before sending it in kubernetes (avoid git in plain text)
- manager secret entirely in vault and expose to the container via volume

View File

@@ -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/).

View File

@@ -0,0 +1,15 @@
## Software development
From years, decades, (centuries !), software development has followed the same principles:
- Development
- Testing
- Packaging
- Shipping
- Deployment
We will see how this map to Kubernetes world.

View File

@@ -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:

17
slides/k8s/stop-manual.md Normal file
View File

@@ -0,0 +1,17 @@
# Automation && CI/CD
What we've done so far:
- development of our application
- manual testing, and exploration of automated testing strategies
- packaging in a container image
- shipping that image to a registry
What still need to be done:
- deployment of our application
- automation of the whole build / ship / run cycle

82
slides/k8s/testing.md Normal file
View File

@@ -0,0 +1,82 @@
# Testing
There are multiple levels of testing:
- unit testing (many small tests that run in isolation),
- integration testing (bigger tests involving multiple components),
- functional or end-to-end testing (even bigger tests involving the whole app).
In this section, we will focus on *unit testing*, where each test case
should (ideally) be completely isolated from other components and system
interaction: no real database, no real backend, *mocks* everywhere.
(For a good discussion on the merits of unit testing, we can read
[Just Say No to More End-to-End Tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html).)
Unfortunately, this ideal scenario is easier said than done ...
---
## Multi-stage build
```dockerfile
FROM <baseimage>
RUN <install dependencies>
COPY <code>
RUN <build code>
RUN <install test dependencies>
COPY <test data sets and fixtures>
RUN <unit tests>
FROM <baseimage>
RUN <install dependencies>
COPY <code>
RUN <build code>
CMD, EXPOSE ...
```
- This leverages the Docker cache: if the code doesn't change, the tests don't need to run
- If the tests require a database or other backend, we can use `docker build --network`
- If the tests fail, the build fails; and no image is generated
---
## Docker Compose
```yaml
version: 3
service:
project:
image: my_image_name
build:
context: .
target: dev
database:
image: redis
backend:
image: backend
```
+
```shell
docker-compose build && docker-compose run project pytest -v
```
---
## Skaffold/Container-structure-test
- The `test` field of the `skaffold.yaml` instructs skaffold to run test against your image.
- It uses the [container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)
- It allows to run custom commands
- Unfortunately, nothing to run other Docker images
(to start a database or a backend that we need to run tests)

View File

@@ -1,7 +1,7 @@
## Versions installed
- Kubernetes 1.15.3
- Docker Engine 19.03.1
- Kubernetes 1.17.2
- 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

View File

@@ -50,7 +50,7 @@ class: extra-details
- *Volumes*:
- appear in Pod specifications (see next slide)
- appear in Pod specifications (we'll see that in a few slides)
- do not exist as API resources (**cannot** do `kubectl get volumes`)
@@ -110,6 +110,8 @@ It runs a single NGINX container.
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})
@@ -175,6 +177,8 @@ spec:
kubectl create -f ~/container.training/k8s/nginx-2-with-volume.yaml
```
<!-- ```bash kubectl wait pod/nginx-with-volume --for condition=ready ``` -->
- Get its IP address:
```bash
IPADDR=$(kubectl get pod nginx-with-volume -o jsonpath={.status.podIP})
@@ -228,7 +232,7 @@ spec:
mountPath: /usr/share/nginx/html/
- name: git
image: alpine
command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ]
command: [ "sh", "-c", "apk add git && git clone https://github.com/octocat/Spoon-Knife /www" ]
volumeMounts:
- name: www
mountPath: /www/
@@ -269,6 +273,11 @@ spec:
kubectl get pods -o wide --watch
```
<!--
```wait NAME```
```tmux split-pane -v```
-->
]
---
@@ -282,14 +291,21 @@ spec:
kubectl create -f ~/container.training/k8s/nginx-3-with-git.yaml
```
<!--
```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
curl `$IP`
```
<!-- ```bash /bin/sleep 5``` -->
- A few seconds later, the state of the pod will change; access it again:
```bash
curl $IP
curl `$IP`
```
]
@@ -399,10 +415,19 @@ spec:
## 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

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