mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-02-15 18:19:56 +00:00
Compare commits
10 Commits
weka
...
devopsdays
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ef6219295 | ||
|
|
346ce0e15c | ||
|
|
964d936435 | ||
|
|
546d9a2986 | ||
|
|
8e5d27b185 | ||
|
|
e8d9e94b72 | ||
|
|
ca980de2fd | ||
|
|
4b2b5ff7e4 | ||
|
|
64fb407e8c | ||
|
|
ea4f46599d |
@@ -1,62 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: consul
|
||||
spec:
|
||||
ports:
|
||||
- port: 8500
|
||||
name: http
|
||||
selector:
|
||||
app: consul
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: consul
|
||||
spec:
|
||||
serviceName: consul
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: consul
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: consul
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- consul
|
||||
topologyKey: kubernetes.io/hostname
|
||||
terminationGracePeriodSeconds: 10
|
||||
containers:
|
||||
- name: consul
|
||||
image: "consul:1.2.2"
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
args:
|
||||
- "agent"
|
||||
- "-bootstrap-expect=3"
|
||||
- "-retry-join=consul-0.consul.$(NAMESPACE).svc.cluster.local"
|
||||
- "-retry-join=consul-1.consul.$(NAMESPACE).svc.cluster.local"
|
||||
- "-retry-join=consul-2.consul.$(NAMESPACE).svc.cluster.local"
|
||||
- "-client=0.0.0.0"
|
||||
- "-data-dir=/consul/data"
|
||||
- "-server"
|
||||
- "-ui"
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- consul leave
|
||||
@@ -1,28 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: build-image
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: docker-build
|
||||
image: docker
|
||||
env:
|
||||
- name: REGISTRY_PORT
|
||||
value: #"30000"
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
apk add --no-cache git &&
|
||||
mkdir /workspace &&
|
||||
git clone https://github.com/jpetazzo/container.training /workspace &&
|
||||
docker build -t localhost:$REGISTRY_PORT/worker /workspace/dockercoins/worker &&
|
||||
docker push localhost:$REGISTRY_PORT/worker
|
||||
volumeMounts:
|
||||
- name: docker-socket
|
||||
mountPath: /var/run/docker.sock
|
||||
volumes:
|
||||
- name: docker-socket
|
||||
hostPath:
|
||||
path: /var/run/docker.sock
|
||||
|
||||
222
k8s/efk.yaml
222
k8s/efk.yaml
@@ -1,222 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: fluentd
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: fluentd
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- namespaces
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: fluentd
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: fluentd
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: fluentd
|
||||
namespace: default
|
||||
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: fluentd
|
||||
labels:
|
||||
k8s-app: fluentd-logging
|
||||
version: v1
|
||||
kubernetes.io/cluster-service: "true"
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: fluentd-logging
|
||||
version: v1
|
||||
kubernetes.io/cluster-service: "true"
|
||||
spec:
|
||||
serviceAccount: fluentd
|
||||
serviceAccountName: fluentd
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: fluentd
|
||||
image: fluent/fluentd-kubernetes-daemonset:elasticsearch
|
||||
env:
|
||||
- name: FLUENT_ELASTICSEARCH_HOST
|
||||
value: "elasticsearch"
|
||||
- name: FLUENT_ELASTICSEARCH_PORT
|
||||
value: "9200"
|
||||
- name: FLUENT_ELASTICSEARCH_SCHEME
|
||||
value: "http"
|
||||
# X-Pack Authentication
|
||||
# =====================
|
||||
- name: FLUENT_ELASTICSEARCH_USER
|
||||
value: "elastic"
|
||||
- name: FLUENT_ELASTICSEARCH_PASSWORD
|
||||
value: "changeme"
|
||||
resources:
|
||||
limits:
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
- name: varlibdockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: varlibdockercontainers
|
||||
hostPath:
|
||||
path: /var/lib/docker/containers
|
||||
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
deployment.kubernetes.io/revision: "1"
|
||||
creationTimestamp: null
|
||||
generation: 1
|
||||
labels:
|
||||
run: elasticsearch
|
||||
name: elasticsearch
|
||||
selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/elasticsearch
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
run: elasticsearch
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: elasticsearch
|
||||
spec:
|
||||
containers:
|
||||
- image: elasticsearch:5.6.8
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: elasticsearch
|
||||
resources: {}
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: elasticsearch
|
||||
name: elasticsearch
|
||||
selfLink: /api/v1/namespaces/default/services/elasticsearch
|
||||
spec:
|
||||
ports:
|
||||
- port: 9200
|
||||
protocol: TCP
|
||||
targetPort: 9200
|
||||
selector:
|
||||
run: elasticsearch
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
deployment.kubernetes.io/revision: "1"
|
||||
creationTimestamp: null
|
||||
generation: 1
|
||||
labels:
|
||||
run: kibana
|
||||
name: kibana
|
||||
selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/kibana
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
run: kibana
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: kibana
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: ELASTICSEARCH_URL
|
||||
value: http://elasticsearch:9200/
|
||||
image: kibana:5.6.8
|
||||
imagePullPolicy: Always
|
||||
name: kibana
|
||||
resources: {}
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: kibana
|
||||
name: kibana
|
||||
selfLink: /api/v1/namespaces/default/services/kibana
|
||||
spec:
|
||||
externalTrafficPolicy: Cluster
|
||||
ports:
|
||||
- port: 5601
|
||||
protocol: TCP
|
||||
targetPort: 5601
|
||||
selector:
|
||||
run: kibana
|
||||
sessionAffinity: None
|
||||
type: NodePort
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kubernetes-dashboard
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
@@ -1,18 +0,0 @@
|
||||
global
|
||||
daemon
|
||||
maxconn 256
|
||||
|
||||
defaults
|
||||
mode tcp
|
||||
timeout connect 5000ms
|
||||
timeout client 50000ms
|
||||
timeout server 50000ms
|
||||
|
||||
frontend the-frontend
|
||||
bind *:80
|
||||
default_backend the-backend
|
||||
|
||||
backend the-backend
|
||||
server google.com-80 google.com:80 maxconn 32 check
|
||||
server bing.com-80 bing.com:80 maxconn 32 check
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: haproxy
|
||||
spec:
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: haproxy
|
||||
containers:
|
||||
- name: haproxy
|
||||
image: haproxy
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /usr/local/etc/haproxy/
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheddar
|
||||
spec:
|
||||
rules:
|
||||
- host: cheddar.A.B.C.D.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: 80
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: kaniko-build
|
||||
spec:
|
||||
initContainers:
|
||||
- name: git-clone
|
||||
image: alpine
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
apk add --no-cache git &&
|
||||
git clone git://github.com/jpetazzo/container.training /workspace
|
||||
volumeMounts:
|
||||
- name: workspace
|
||||
mountPath: /workspace
|
||||
containers:
|
||||
- name: build-image
|
||||
image: gcr.io/kaniko-project/executor:latest
|
||||
args:
|
||||
- "--context=/workspace/dockercoins/rng"
|
||||
- "--skip-tls-verify"
|
||||
- "--destination=registry:5000/rng-kaniko:latest"
|
||||
volumeMounts:
|
||||
- name: workspace
|
||||
mountPath: /workspace
|
||||
volumes:
|
||||
- name: workspace
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
# Copyright 2017 The Kubernetes Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Configuration to deploy release version of the Dashboard UI compatible with
|
||||
# Kubernetes 1.8.
|
||||
#
|
||||
# Example usage: kubectl create -f <this_file>
|
||||
|
||||
# ------------------- Dashboard Secret ------------------- #
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard-certs
|
||||
namespace: kube-system
|
||||
type: Opaque
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Service Account ------------------- #
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Role & Role Binding ------------------- #
|
||||
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: kubernetes-dashboard-minimal
|
||||
namespace: kube-system
|
||||
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"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kubernetes-dashboard-minimal
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kubernetes-dashboard-minimal
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
# ------------------- Dashboard Deployment ------------------- #
|
||||
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1beta2
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
name: kubernetes-dashboard
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kubernetes-dashboard
|
||||
spec:
|
||||
containers:
|
||||
- name: kubernetes-dashboard
|
||||
image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
protocol: TCP
|
||||
args:
|
||||
- --auto-generate-certificates
|
||||
# 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
|
||||
volumes:
|
||||
- name: kubernetes-dashboard-certs
|
||||
secret:
|
||||
secretName: kubernetes-dashboard-certs
|
||||
- name: tmp-volume
|
||||
emptyDir: {}
|
||||
serviceAccountName: kubernetes-dashboard
|
||||
# Comment the following tolerations if Dashboard must not be deployed on master
|
||||
tolerations:
|
||||
- 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
|
||||
spec:
|
||||
ports:
|
||||
- port: 443
|
||||
targetPort: 8443
|
||||
selector:
|
||||
k8s-app: kubernetes-dashboard
|
||||
@@ -1,14 +0,0 @@
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: allow-testcurl-for-testweb
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
run: testweb
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
run: testcurl
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: deny-all-for-testweb
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
run: testweb
|
||||
ingress: []
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: deny-from-other-namespaces
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector: {}
|
||||
---
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: allow-webui
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
run: webui
|
||||
ingress:
|
||||
- from: []
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx-with-volume
|
||||
spec:
|
||||
volumes:
|
||||
- name: www
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /usr/share/nginx/html/
|
||||
- name: git
|
||||
image: alpine
|
||||
command: [ "sh", "-c", "apk add --no-cache git && git clone https://github.com/octocat/Spoon-Knife /www" ]
|
||||
volumeMounts:
|
||||
- name: www
|
||||
mountPath: /www/
|
||||
restartPolicy: OnFailure
|
||||
|
||||
@@ -1,580 +0,0 @@
|
||||
# SOURCE: https://install.portworx.com/?kbver=1.11.2&b=true&s=/dev/loop0&c=px-workshop&stork=true&lh=true
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: stork-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
policy.cfg: |-
|
||||
{
|
||||
"kind": "Policy",
|
||||
"apiVersion": "v1",
|
||||
"extenders": [
|
||||
{
|
||||
"urlPrefix": "http://stork-service.kube-system.svc:8099",
|
||||
"apiVersion": "v1beta1",
|
||||
"filterVerb": "filter",
|
||||
"prioritizeVerb": "prioritize",
|
||||
"weight": 5,
|
||||
"enableHttps": false,
|
||||
"nodeCacheCapable": false
|
||||
}
|
||||
]
|
||||
}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: stork-account
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: stork-role
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "delete"]
|
||||
- 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: ["apiextensions.k8s.io"]
|
||||
resources: ["customresourcedefinitions"]
|
||||
verbs: ["create", "list", "watch", "delete"]
|
||||
- apiGroups: ["volumesnapshot.external-storage.k8s.io"]
|
||||
resources: ["volumesnapshots"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["volumesnapshot.external-storage.k8s.io"]
|
||||
resources: ["volumesnapshotdatas"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
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"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: stork-role-binding
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: stork-account
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: stork-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: stork-service
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
name: stork
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8099
|
||||
targetPort: 8099
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
labels:
|
||||
tier: control-plane
|
||||
name: stork
|
||||
namespace: kube-system
|
||||
spec:
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
replicas: 3
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
labels:
|
||||
name: stork
|
||||
tier: control-plane
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /stork
|
||||
- --driver=pxd
|
||||
- --verbose
|
||||
- --leader-elect=true
|
||||
- --health-monitor-interval=120
|
||||
imagePullPolicy: Always
|
||||
image: openstorage/stork:1.1.3
|
||||
resources:
|
||||
requests:
|
||||
cpu: '0.1'
|
||||
name: stork
|
||||
hostPID: false
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: "name"
|
||||
operator: In
|
||||
values:
|
||||
- stork
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
serviceAccountName: stork-account
|
||||
---
|
||||
kind: StorageClass
|
||||
apiVersion: storage.k8s.io/v1
|
||||
metadata:
|
||||
name: stork-snapshot-sc
|
||||
provisioner: stork-snapshot
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: stork-scheduler-account
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: stork-scheduler-role
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["get", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["create", "patch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["create"]
|
||||
- apiGroups: [""]
|
||||
resourceNames: ["kube-scheduler"]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["delete", "get", "patch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["delete", "get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["bindings", "pods/binding"]
|
||||
verbs: ["create"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/status"]
|
||||
verbs: ["patch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["replicationcontrollers", "services"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["app", "extensions"]
|
||||
resources: ["replicasets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["statefulsets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["policy"]
|
||||
resources: ["poddisruptionbudgets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims", "persistentvolumes"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["storage.k8s.io"]
|
||||
resources: ["storageclasses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: stork-scheduler-role-binding
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: stork-scheduler-account
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: stork-scheduler-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
component: scheduler
|
||||
tier: control-plane
|
||||
name: stork-scheduler
|
||||
name: stork-scheduler
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: scheduler
|
||||
tier: control-plane
|
||||
name: stork-scheduler
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /usr/local/bin/kube-scheduler
|
||||
- --address=0.0.0.0
|
||||
- --leader-elect=true
|
||||
- --scheduler-name=stork
|
||||
- --policy-configmap=stork-config
|
||||
- --policy-configmap-namespace=kube-system
|
||||
- --lock-object-name=stork-scheduler
|
||||
image: gcr.io/google_containers/kube-scheduler-amd64:v1.11.2
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10251
|
||||
initialDelaySeconds: 15
|
||||
name: stork-scheduler
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10251
|
||||
resources:
|
||||
requests:
|
||||
cpu: '0.1'
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: "name"
|
||||
operator: In
|
||||
values:
|
||||
- stork-scheduler
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
hostPID: false
|
||||
serviceAccountName: stork-scheduler-account
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: portworx-service
|
||||
namespace: kube-system
|
||||
labels:
|
||||
name: portworx
|
||||
spec:
|
||||
selector:
|
||||
name: portworx
|
||||
ports:
|
||||
- name: px-api
|
||||
protocol: TCP
|
||||
port: 9001
|
||||
targetPort: 9001
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: px-account
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: node-get-put-list-role
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["watch", "get", "update", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["delete", "get", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims", "persistentvolumes"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["get", "list", "update", "create"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["podsecuritypolicies"]
|
||||
resourceNames: ["privileged"]
|
||||
verbs: ["use"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: node-role-binding
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: px-account
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: node-get-put-list-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: portworx
|
||||
---
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: px-role
|
||||
namespace: portworx
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "create", "update", "patch"]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: px-role-binding
|
||||
namespace: portworx
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: px-account
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: px-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: portworx
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
portworx.com/install-source: "https://install.portworx.com/?kbver=1.11.2&b=true&s=/dev/loop0&c=px-workshop&stork=true&lh=true"
|
||||
spec:
|
||||
minReadySeconds: 0
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: portworx
|
||||
spec:
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: px/enabled
|
||||
operator: NotIn
|
||||
values:
|
||||
- "false"
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: DoesNotExist
|
||||
hostNetwork: true
|
||||
hostPID: false
|
||||
containers:
|
||||
- name: portworx
|
||||
image: portworx/oci-monitor:1.4.2.2
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
["-c", "px-workshop", "-s", "/dev/loop0", "-b",
|
||||
"-x", "kubernetes"]
|
||||
env:
|
||||
- name: "PX_TEMPLATE_VERSION"
|
||||
value: "v4"
|
||||
|
||||
livenessProbe:
|
||||
periodSeconds: 30
|
||||
initialDelaySeconds: 840 # allow image pull in slow networks
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /status
|
||||
port: 9001
|
||||
readinessProbe:
|
||||
periodSeconds: 10
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /health
|
||||
port: 9015
|
||||
terminationMessagePath: "/tmp/px-termination-log"
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: dockersock
|
||||
mountPath: /var/run/docker.sock
|
||||
- name: etcpwx
|
||||
mountPath: /etc/pwx
|
||||
- name: optpwx
|
||||
mountPath: /opt/pwx
|
||||
- name: proc1nsmount
|
||||
mountPath: /host_proc/1/ns
|
||||
- name: sysdmount
|
||||
mountPath: /etc/systemd/system
|
||||
- name: diagsdump
|
||||
mountPath: /var/cores
|
||||
- name: journalmount1
|
||||
mountPath: /var/run/log
|
||||
readOnly: true
|
||||
- name: journalmount2
|
||||
mountPath: /var/log
|
||||
readOnly: true
|
||||
- name: dbusmount
|
||||
mountPath: /var/run/dbus
|
||||
restartPolicy: Always
|
||||
serviceAccountName: px-account
|
||||
volumes:
|
||||
- name: dockersock
|
||||
hostPath:
|
||||
path: /var/run/docker.sock
|
||||
- name: etcpwx
|
||||
hostPath:
|
||||
path: /etc/pwx
|
||||
- name: optpwx
|
||||
hostPath:
|
||||
path: /opt/pwx
|
||||
- name: proc1nsmount
|
||||
hostPath:
|
||||
path: /proc/1/ns
|
||||
- name: sysdmount
|
||||
hostPath:
|
||||
path: /etc/systemd/system
|
||||
- name: diagsdump
|
||||
hostPath:
|
||||
path: /var/cores
|
||||
- name: journalmount1
|
||||
hostPath:
|
||||
path: /var/run/log
|
||||
- name: journalmount2
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: dbusmount
|
||||
hostPath:
|
||||
path: /var/run/dbus
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: px-lh-account
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: px-lh-role
|
||||
namespace: kube-system
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["get", "create", "update"]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: px-lh-role-binding
|
||||
namespace: kube-system
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: px-lh-account
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: px-lh-role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: px-lighthouse
|
||||
namespace: kube-system
|
||||
labels:
|
||||
tier: px-web-console
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
nodePort: 32678
|
||||
- name: https
|
||||
port: 443
|
||||
nodePort: 32679
|
||||
selector:
|
||||
tier: px-web-console
|
||||
---
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: px-lighthouse
|
||||
namespace: kube-system
|
||||
labels:
|
||||
tier: px-web-console
|
||||
spec:
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: px-web-console
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
tier: px-web-console
|
||||
spec:
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: portworx/lh-config-sync:0.2
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "init"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config/lh
|
||||
containers:
|
||||
- name: px-lighthouse
|
||||
image: portworx/px-lighthouse:1.5.0
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 443
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config/lh
|
||||
- name: config-sync
|
||||
image: portworx/lh-config-sync:0.2
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "sync"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config/lh
|
||||
serviceAccountName: px-lh-account
|
||||
volumes:
|
||||
- name: config
|
||||
emptyDir: {}
|
||||
@@ -1,30 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgres
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
serviceName: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
schedulerName: stork
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:10.5
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
name: postgres
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: postgres
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: registry
|
||||
spec:
|
||||
containers:
|
||||
- name: registry
|
||||
image: registry
|
||||
env:
|
||||
- name: REGISTRY_HTTP_ADDR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: registry
|
||||
key: http.addr
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
deployment.kubernetes.io/revision: "2"
|
||||
creationTimestamp: null
|
||||
generation: 1
|
||||
labels:
|
||||
run: socat
|
||||
name: socat
|
||||
namespace: kube-system
|
||||
selfLink: /apis/extensions/v1beta1/namespaces/kube-system/deployments/socat
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
run: socat
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: socat
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- sh
|
||||
- -c
|
||||
- apk add --no-cache socat && socat TCP-LISTEN:80,fork,reuseaddr OPENSSL:kubernetes-dashboard:443,verify=0
|
||||
image: alpine
|
||||
imagePullPolicy: Always
|
||||
name: socat
|
||||
resources: {}
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
status: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: socat
|
||||
name: socat
|
||||
namespace: kube-system
|
||||
selfLink: /api/v1/namespaces/kube-system/services/socat
|
||||
spec:
|
||||
externalTrafficPolicy: Cluster
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
run: socat
|
||||
sessionAffinity: None
|
||||
type: NodePort
|
||||
status:
|
||||
loadBalancer: {}
|
||||
@@ -1,11 +0,0 @@
|
||||
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"
|
||||
|
||||
100
k8s/traefik.yaml
100
k8s/traefik.yaml
@@ -1,100 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
namespace: kube-system
|
||||
---
|
||||
kind: DaemonSet
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
spec:
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
hostNetwork: true
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
hostPort: 80
|
||||
- name: admin
|
||||
containerPort: 8080
|
||||
hostPort: 8080
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
args:
|
||||
- --api
|
||||
- --kubernetes
|
||||
- --logLevel=INFO
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: traefik-ingress-service
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
name: web
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
name: admin
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: traefik-ingress-controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: traefik-ingress-controller
|
||||
namespace: kube-system
|
||||
@@ -93,7 +93,7 @@ wrap Run this program in a container
|
||||
- The `./workshopctl` script can be executed directly.
|
||||
- It will run locally if all its dependencies are fulfilled; otherwise it will run in the Docker container you created with `docker-compose build` (preparevms_prepare-vms).
|
||||
- During `start` it will add your default local SSH key to all instances under the `ubuntu` user.
|
||||
- During `deploy` it will create the `docker` user with password `training`, which is printing on the cards for students. This can be configured with the `docker_user_password` property in the settings file.
|
||||
- During `deploy` it will create the `docker` user with password `training`, which is printing on the cards for students. For now, this is hard coded.
|
||||
|
||||
### Example Steps to Launch a Batch of AWS Instances for a Workshop
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ img {
|
||||
<tr><td>login:</td></tr>
|
||||
<tr><td class="logpass">docker</td></tr>
|
||||
<tr><td>password:</td></tr>
|
||||
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
|
||||
<tr><td class="logpass">training</td></tr>
|
||||
</table>
|
||||
|
||||
</p>
|
||||
|
||||
@@ -168,22 +168,6 @@ _cmd_kube() {
|
||||
sudo kubeadm join --discovery-token-unsafe-skip-ca-verification --token \$TOKEN node1:6443
|
||||
fi"
|
||||
|
||||
# Install stern
|
||||
pssh "
|
||||
if [ ! -x /usr/local/bin/stern ]; then
|
||||
sudo curl -L -o /usr/local/bin/stern https://github.com/wercker/stern/releases/download/1.8.0/stern_linux_amd64
|
||||
sudo chmod +x /usr/local/bin/stern
|
||||
stern --completion bash | sudo tee /etc/bash_completion.d/stern
|
||||
fi"
|
||||
|
||||
# Install helm
|
||||
pssh "
|
||||
if [ ! -x /usr/local/bin/helm ]; then
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | sudo bash
|
||||
helm completion bash | sudo tee /etc/bash_completion.d/helm
|
||||
fi"
|
||||
|
||||
|
||||
sep "Done"
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ COMPOSE_VERSION = config["compose_version"]
|
||||
MACHINE_VERSION = config["machine_version"]
|
||||
CLUSTER_SIZE = config["clustersize"]
|
||||
ENGINE_VERSION = config["engine_version"]
|
||||
DOCKER_USER_PASSWORD = config["docker_user_password"]
|
||||
|
||||
#################################
|
||||
|
||||
@@ -55,9 +54,9 @@ system("curl --silent {} > /tmp/ipv4".format(ipv4_retrieval_endpoint))
|
||||
|
||||
ipv4 = open("/tmp/ipv4").read()
|
||||
|
||||
# Add a "docker" user with password coming from the settings
|
||||
# Add a "docker" user with password "training"
|
||||
system("id docker || sudo useradd -d /home/docker -m -s /bin/bash docker")
|
||||
system("echo docker:{} | sudo chpasswd".format(DOCKER_USER_PASSWORD))
|
||||
system("echo docker:training | sudo chpasswd")
|
||||
|
||||
# Fancy prompt courtesy of @soulshake.
|
||||
system("""sudo -u docker tee -a /home/docker/.bashrc <<SQRL
|
||||
|
||||
@@ -22,6 +22,3 @@ engine_version: test
|
||||
# These correspond to the version numbers visible on their respective GitHub release pages
|
||||
compose_version: 1.18.0
|
||||
machine_version: 0.13.0
|
||||
|
||||
# Password used to connect with the "docker user"
|
||||
docker_user_password: training
|
||||
|
||||
@@ -20,8 +20,5 @@ paper_margin: 0.2in
|
||||
engine_version: stable
|
||||
|
||||
# These correspond to the version numbers visible on their respective GitHub release pages
|
||||
compose_version: 1.22.0
|
||||
machine_version: 0.15.0
|
||||
|
||||
# Password used to connect with the "docker user"
|
||||
docker_user_password: training
|
||||
compose_version: 1.21.1
|
||||
machine_version: 0.14.0
|
||||
|
||||
@@ -85,7 +85,7 @@ img {
|
||||
<tr><td>login:</td></tr>
|
||||
<tr><td class="logpass">docker</td></tr>
|
||||
<tr><td>password:</td></tr>
|
||||
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
|
||||
<tr><td class="logpass">training</td></tr>
|
||||
</table>
|
||||
|
||||
</p>
|
||||
|
||||
@@ -22,6 +22,3 @@ engine_version: stable
|
||||
# These correspond to the version numbers visible on their respective GitHub release pages
|
||||
compose_version: 1.21.1
|
||||
machine_version: 0.14.0
|
||||
|
||||
# Password used to connect with the "docker user"
|
||||
docker_user_password: training
|
||||
@@ -22,6 +22,3 @@ engine_version: stable
|
||||
# These correspond to the version numbers visible on their respective GitHub release pages
|
||||
compose_version: 1.21.1
|
||||
machine_version: 0.14.0
|
||||
|
||||
# Password used to connect with the "docker user"
|
||||
docker_user_password: training
|
||||
@@ -1 +1,2 @@
|
||||
/ /weka.yml.html 200!
|
||||
/ /kube-90min.yml.html 200!
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ class State(object):
|
||||
self.interactive = True
|
||||
self.verify_status = False
|
||||
self.simulate_type = True
|
||||
self.switch_desktop = False
|
||||
self.sync_slides = False
|
||||
self.open_links = False
|
||||
self.run_hidden = True
|
||||
self.slide = 1
|
||||
self.snippet = 0
|
||||
|
||||
@@ -41,10 +37,6 @@ class State(object):
|
||||
self.interactive = bool(data["interactive"])
|
||||
self.verify_status = bool(data["verify_status"])
|
||||
self.simulate_type = bool(data["simulate_type"])
|
||||
self.switch_desktop = bool(data["switch_desktop"])
|
||||
self.sync_slides = bool(data["sync_slides"])
|
||||
self.open_links = bool(data["open_links"])
|
||||
self.run_hidden = bool(data["run_hidden"])
|
||||
self.slide = int(data["slide"])
|
||||
self.snippet = int(data["snippet"])
|
||||
|
||||
@@ -54,10 +46,6 @@ class State(object):
|
||||
interactive=self.interactive,
|
||||
verify_status=self.verify_status,
|
||||
simulate_type=self.simulate_type,
|
||||
switch_desktop=self.switch_desktop,
|
||||
sync_slides=self.sync_slides,
|
||||
open_links=self.open_links,
|
||||
run_hidden=self.run_hidden,
|
||||
slide=self.slide,
|
||||
snippet=self.snippet,
|
||||
), f, default_flow_style=False)
|
||||
@@ -134,20 +122,14 @@ class Slide(object):
|
||||
|
||||
|
||||
def focus_slides():
|
||||
if not state.switch_desktop:
|
||||
return
|
||||
subprocess.check_output(["i3-msg", "workspace", "3"])
|
||||
subprocess.check_output(["i3-msg", "workspace", "1"])
|
||||
|
||||
def focus_terminal():
|
||||
if not state.switch_desktop:
|
||||
return
|
||||
subprocess.check_output(["i3-msg", "workspace", "2"])
|
||||
subprocess.check_output(["i3-msg", "workspace", "1"])
|
||||
|
||||
def focus_browser():
|
||||
if not state.switch_desktop:
|
||||
return
|
||||
subprocess.check_output(["i3-msg", "workspace", "4"])
|
||||
subprocess.check_output(["i3-msg", "workspace", "1"])
|
||||
|
||||
@@ -325,21 +307,17 @@ while True:
|
||||
slide = slides[state.slide]
|
||||
snippet = slide.snippets[state.snippet-1] if state.snippet else None
|
||||
click.clear()
|
||||
print("[Slide {}/{}] [Snippet {}/{}] [simulate_type:{}] [verify_status:{}] "
|
||||
"[switch_desktop:{}] [sync_slides:{}] [open_links:{}] [run_hidden:{}]"
|
||||
print("[Slide {}/{}] [Snippet {}/{}] [simulate_type:{}] [verify_status:{}]"
|
||||
.format(state.slide, len(slides)-1,
|
||||
state.snippet, len(slide.snippets) if slide.snippets else 0,
|
||||
state.simulate_type, state.verify_status,
|
||||
state.switch_desktop, state.sync_slides,
|
||||
state.open_links, state.run_hidden))
|
||||
state.simulate_type, state.verify_status))
|
||||
print(hrule())
|
||||
if snippet:
|
||||
print(slide.content.replace(snippet.content, ansi(7)(snippet.content)))
|
||||
focus_terminal()
|
||||
else:
|
||||
print(slide.content)
|
||||
if state.sync_slides:
|
||||
subprocess.check_output(["./gotoslide.js", str(slide.number)])
|
||||
subprocess.check_output(["./gotoslide.js", str(slide.number)])
|
||||
focus_slides()
|
||||
print(hrule())
|
||||
if state.interactive:
|
||||
@@ -348,10 +326,6 @@ while True:
|
||||
print("n/→ Next")
|
||||
print("s Simulate keystrokes")
|
||||
print("v Validate exit status")
|
||||
print("d Switch desktop")
|
||||
print("k Sync slides")
|
||||
print("o Open links")
|
||||
print("h Run hidden commands")
|
||||
print("g Go to a specific slide")
|
||||
print("q Quit")
|
||||
print("c Continue non-interactively until next error")
|
||||
@@ -367,14 +341,6 @@ while True:
|
||||
state.simulate_type = not state.simulate_type
|
||||
elif command == "v":
|
||||
state.verify_status = not state.verify_status
|
||||
elif command == "d":
|
||||
state.switch_desktop = not state.switch_desktop
|
||||
elif command == "k":
|
||||
state.sync_slides = not state.sync_slides
|
||||
elif command == "o":
|
||||
state.open_links = not state.open_links
|
||||
elif command == "h":
|
||||
state.run_hidden = not state.run_hidden
|
||||
elif command == "g":
|
||||
state.slide = click.prompt("Enter slide number", type=int)
|
||||
state.snippet = 0
|
||||
@@ -400,7 +366,7 @@ while True:
|
||||
logging.info("Running with method {}: {}".format(method, data))
|
||||
if method == "keys":
|
||||
send_keys(data)
|
||||
elif method == "bash" or (method == "hide" and state.run_hidden):
|
||||
elif method == "bash":
|
||||
# Make sure that we're ready
|
||||
wait_for_prompt()
|
||||
# Strip leading spaces
|
||||
@@ -439,12 +405,11 @@ while True:
|
||||
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()
|
||||
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()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
click
|
||||
41
slides/common/prereqs.md
Normal file
41
slides/common/prereqs.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## Hands-on
|
||||
|
||||
- All hands-on sections are clearly identified, like the gray rectangle below
|
||||
|
||||
.exercise[
|
||||
|
||||
- This is the stuff you're supposed to do!
|
||||
|
||||
- Go to @@SLIDES@@ to view these slides
|
||||
|
||||
]
|
||||
|
||||
- Each person gets a private cluster of cloud VMs (not shared with anybody else)
|
||||
|
||||
- All you need is a computer (or even a phone or tablet!), with:
|
||||
|
||||
- an internet connection
|
||||
|
||||
- a web browser
|
||||
|
||||
- an SSH client
|
||||
|
||||
---
|
||||
|
||||
class: in-person
|
||||
|
||||
## Connecting to our lab environment
|
||||
|
||||
.exercise[
|
||||
|
||||
- Log into the first VM (`node1`) with your SSH client
|
||||
|
||||
- Check that you can SSH (without password) to `node2`:
|
||||
```bash
|
||||
ssh node2
|
||||
```
|
||||
- Type `exit` or `^D` to come back to `node1`
|
||||
|
||||
]
|
||||
|
||||
If anything goes wrong — ask for help!
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
<!--
|
||||
```bash
|
||||
cd ~
|
||||
if [ -d container.training ]; then
|
||||
mv container.training container.training.$RANDOM
|
||||
mv container.training container.training.$$
|
||||
fi
|
||||
```
|
||||
-->
|
||||
@@ -95,61 +94,6 @@ class: extra-details
|
||||
|
||||
---
|
||||
|
||||
## Service discovery in container-land
|
||||
|
||||
- We do not hard-code IP addresses in the code
|
||||
|
||||
- We do not hard-code FQDN in the code, either
|
||||
|
||||
- We just connect to a service name, and container-magic does the rest
|
||||
|
||||
(And by container-magic, we mean "a crafty, dynamic, embedded DNS server")
|
||||
|
||||
---
|
||||
|
||||
## Example in `worker/worker.py`
|
||||
|
||||
```python
|
||||
redis = Redis("`redis`")
|
||||
|
||||
|
||||
def get_random_bytes():
|
||||
r = requests.get("http://`rng`/32")
|
||||
return r.content
|
||||
|
||||
|
||||
def hash_bytes(data):
|
||||
r = requests.post("http://`hasher`/",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/octet-stream"})
|
||||
```
|
||||
|
||||
(Full source code available [here](
|
||||
https://@@GITREPO@@/blob/8279a3bce9398f7c1a53bdd95187c53eda4e6435/dockercoins/worker/worker.py#L17
|
||||
))
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Links, naming, and service discovery
|
||||
|
||||
- Containers can have network aliases (resolvable through DNS)
|
||||
|
||||
- Compose file version 2+ makes each container reachable through its service name
|
||||
|
||||
- Compose file version 1 did require "links" sections
|
||||
|
||||
- Network aliases are automatically namespaced
|
||||
|
||||
- you can have multiple apps declaring and using a service named `database`
|
||||
|
||||
- containers in the blue app will resolve `database` to the IP of the blue database
|
||||
|
||||
- containers in the green app will resolve `database` to the IP of the green database
|
||||
|
||||
---
|
||||
|
||||
## What's this application?
|
||||
|
||||
--
|
||||
21
slides/common/title.md
Normal file
21
slides/common/title.md
Normal file
@@ -0,0 +1,21 @@
|
||||
class: title, self-paced
|
||||
|
||||
@@TITLE@@
|
||||
|
||||
.nav[*Self-paced version*]
|
||||
|
||||
---
|
||||
|
||||
class: title, in-person
|
||||
|
||||
@@TITLE@@<br/></br>
|
||||
|
||||
.footnote[
|
||||
**Be kind to the WiFi!**<br/>
|
||||
<!-- *Use the 5G network.* -->
|
||||
*Don't use your hotspot.*<br/>
|
||||
*Don't stream videos or download big files during the workshop.*<br/>
|
||||
*Thank you!*
|
||||
|
||||
**Slides: @@SLIDES@@**
|
||||
]
|
||||
@@ -51,7 +51,7 @@ for line in open(sys.argv[1]):
|
||||
|
||||
state.show()
|
||||
|
||||
for chapter in sorted(state.chapters, key=lambda f: int(f.split("-")[1])):
|
||||
for chapter in sorted(state.chapters):
|
||||
chapter_size = sum(state.sections[s] for s in state.chapters[chapter])
|
||||
print("{}\t{}\t{}".format("total size for", chapter, chapter_size))
|
||||
|
||||
|
||||
@@ -113,13 +113,7 @@ for item in items:
|
||||
1: "st", 2: "nd", 3: "rd",
|
||||
21: "st", 22: "nd", 23: "rd",
|
||||
31: "st"}.get(date.day, "th")
|
||||
# %e is a non-standard extension (it displays the day, but without a
|
||||
# leading zero). If strftime fails with ValueError, try to fall back
|
||||
# on %d (which displays the day but with a leading zero when needed).
|
||||
try:
|
||||
item["prettydate"] = date.strftime("%B %e{}, %Y").format(suffix)
|
||||
except ValueError:
|
||||
item["prettydate"] = date.strftime("%B %d{}, %Y").format(suffix)
|
||||
item["prettydate"] = date.strftime("%B %e{}, %Y").format(suffix)
|
||||
|
||||
today = datetime.date.today()
|
||||
coming_soon = [i for i in items if i.get("date") and i["date"] >= today]
|
||||
|
||||
@@ -1,50 +1,9 @@
|
||||
- date: 2018-11-23
|
||||
city: Copenhagen
|
||||
country: dk
|
||||
event: GOTO
|
||||
title: Build Container Orchestration with Docker Swarm
|
||||
speaker: bretfisher
|
||||
attend: https://gotocph.com/2018/workshops/121
|
||||
|
||||
- date: 2018-11-08
|
||||
city: San Francisco, CA
|
||||
country: us
|
||||
event: QCON
|
||||
title: Introduction to Docker and Containers
|
||||
speaker: jpetazzo
|
||||
attend: https://qconsf.com/sf2018/workshop/introduction-docker-and-containers
|
||||
|
||||
- date: 2018-11-09
|
||||
city: San Francisco, CA
|
||||
country: us
|
||||
event: QCON
|
||||
title: Getting Started With Kubernetes and Container Orchestration
|
||||
speaker: jpetazzo
|
||||
attend: https://qconsf.com/sf2018/workshop/getting-started-kubernetes-and-container-orchestration
|
||||
|
||||
- date: 2018-10-31
|
||||
city: London, UK
|
||||
country: uk
|
||||
event: Velocity EU
|
||||
title: Kubernetes 101
|
||||
speaker: bridgetkromhout
|
||||
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/71149
|
||||
|
||||
- date: 2018-10-30
|
||||
city: London, UK
|
||||
country: uk
|
||||
event: Velocity EU
|
||||
title: "Docker Zero to Hero: Docker, Compose and Production Swarm"
|
||||
speaker: bretfisher
|
||||
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/71231
|
||||
|
||||
- date: 2018-07-12
|
||||
city: Minneapolis, MN
|
||||
country: us
|
||||
event: devopsdays Minneapolis
|
||||
title: Kubernetes 101
|
||||
speaker: "ashleymcnamara, bketelsen"
|
||||
slides: https://devopsdaysmsp2018.container.training
|
||||
attend: https://www.devopsdays.org/events/2018-minneapolis/registration/
|
||||
|
||||
- date: 2018-10-01
|
||||
@@ -63,30 +22,12 @@
|
||||
speaker: jpetazzo
|
||||
attend: https://conferences.oreilly.com/velocity/vl-ny/public/schedule/detail/69875
|
||||
|
||||
- date: 2018-09-30
|
||||
city: New York, NY
|
||||
country: us
|
||||
event: Velocity
|
||||
title: "Docker Zero to Hero: Docker, Compose and Production Swarm"
|
||||
speaker: bretfisher
|
||||
attend: https://conferences.oreilly.com/velocity/vl-ny/public/schedule/detail/70147
|
||||
|
||||
- date: 2018-09-17
|
||||
country: fr
|
||||
city: Paris
|
||||
event: ENIX SAS
|
||||
speaker: jpetazzo
|
||||
title: Déployer ses applications avec Kubernetes (in French)
|
||||
lang: fr
|
||||
attend: https://enix.io/fr/services/formation/deployer-ses-applications-avec-kubernetes/
|
||||
|
||||
- date: 2018-07-17
|
||||
city: Portland, OR
|
||||
country: us
|
||||
event: OSCON
|
||||
title: Kubernetes 101
|
||||
speaker: bridgetkromhout
|
||||
slides: https://oscon2018.container.training/
|
||||
attend: https://conferences.oreilly.com/oscon/oscon-or/public/schedule/detail/66287
|
||||
|
||||
- date: 2018-06-27
|
||||
|
||||
@@ -13,47 +13,47 @@ exclude:
|
||||
- self-paced
|
||||
|
||||
chapters:
|
||||
- shared/title.md
|
||||
- common/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/Multi_Stage_Builds.md
|
||||
- containers/Publishing_To_Docker_Hub.md
|
||||
- containers/Dockerfile_Tips.md
|
||||
- - containers/Naming_And_Inspecting.md
|
||||
- containers/Labels.md
|
||||
- containers/Getting_Inside.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/Working_With_Volumes.md
|
||||
- containers/Compose_For_Dev_Stacks.md
|
||||
- containers/Docker_Machine.md
|
||||
- - containers/Advanced_Dockerfiles.md
|
||||
- containers/Application_Configuration.md
|
||||
- containers/Logging.md
|
||||
- containers/Resource_Limits.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
|
||||
- intro/intro.md
|
||||
- common/about-slides.md
|
||||
- common/toc.md
|
||||
- - intro/Docker_Overview.md
|
||||
- intro/Docker_History.md
|
||||
- intro/Training_Environment.md
|
||||
- intro/Installing_Docker.md
|
||||
- intro/First_Containers.md
|
||||
- intro/Background_Containers.md
|
||||
- intro/Start_And_Attach.md
|
||||
- - intro/Initial_Images.md
|
||||
- intro/Building_Images_Interactively.md
|
||||
- intro/Building_Images_With_Dockerfiles.md
|
||||
- intro/Cmd_And_Entrypoint.md
|
||||
- intro/Copying_Files_During_Build.md
|
||||
- - intro/Multi_Stage_Builds.md
|
||||
- intro/Publishing_To_Docker_Hub.md
|
||||
- intro/Dockerfile_Tips.md
|
||||
- - intro/Naming_And_Inspecting.md
|
||||
- intro/Labels.md
|
||||
- intro/Getting_Inside.md
|
||||
- - intro/Container_Networking_Basics.md
|
||||
- intro/Network_Drivers.md
|
||||
- intro/Container_Network_Model.md
|
||||
#- intro/Connecting_Containers_With_Links.md
|
||||
- intro/Ambassadors.md
|
||||
- - intro/Local_Development_Workflow.md
|
||||
- intro/Working_With_Volumes.md
|
||||
- intro/Compose_For_Dev_Stacks.md
|
||||
- intro/Docker_Machine.md
|
||||
- - intro/Advanced_Dockerfiles.md
|
||||
- intro/Application_Configuration.md
|
||||
- intro/Logging.md
|
||||
- intro/Resource_Limits.md
|
||||
- - intro/Namespaces_Cgroups.md
|
||||
- intro/Copy_On_Write.md
|
||||
#- intro/Containers_From_Scratch.md
|
||||
- - intro/Container_Engines.md
|
||||
- intro/Ecosystem.md
|
||||
- intro/Orchestration_Overview.md
|
||||
- common/thankyou.md
|
||||
- intro/links.md
|
||||
|
||||
@@ -13,47 +13,47 @@ exclude:
|
||||
- in-person
|
||||
|
||||
chapters:
|
||||
- shared/title.md
|
||||
# - shared/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/Multi_Stage_Builds.md
|
||||
- containers/Publishing_To_Docker_Hub.md
|
||||
- containers/Dockerfile_Tips.md
|
||||
- - containers/Naming_And_Inspecting.md
|
||||
- containers/Labels.md
|
||||
- containers/Getting_Inside.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/Working_With_Volumes.md
|
||||
- containers/Compose_For_Dev_Stacks.md
|
||||
- containers/Docker_Machine.md
|
||||
- - containers/Advanced_Dockerfiles.md
|
||||
- containers/Application_Configuration.md
|
||||
- containers/Logging.md
|
||||
- containers/Resource_Limits.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
|
||||
- common/title.md
|
||||
# - common/logistics.md
|
||||
- intro/intro.md
|
||||
- common/about-slides.md
|
||||
- common/toc.md
|
||||
- - intro/Docker_Overview.md
|
||||
- intro/Docker_History.md
|
||||
- intro/Training_Environment.md
|
||||
- intro/Installing_Docker.md
|
||||
- intro/First_Containers.md
|
||||
- intro/Background_Containers.md
|
||||
- intro/Start_And_Attach.md
|
||||
- - intro/Initial_Images.md
|
||||
- intro/Building_Images_Interactively.md
|
||||
- intro/Building_Images_With_Dockerfiles.md
|
||||
- intro/Cmd_And_Entrypoint.md
|
||||
- intro/Copying_Files_During_Build.md
|
||||
- - intro/Multi_Stage_Builds.md
|
||||
- intro/Publishing_To_Docker_Hub.md
|
||||
- intro/Dockerfile_Tips.md
|
||||
- - intro/Naming_And_Inspecting.md
|
||||
- intro/Labels.md
|
||||
- intro/Getting_Inside.md
|
||||
- - intro/Container_Networking_Basics.md
|
||||
- intro/Network_Drivers.md
|
||||
- intro/Container_Network_Model.md
|
||||
#- intro/Connecting_Containers_With_Links.md
|
||||
- intro/Ambassadors.md
|
||||
- - intro/Local_Development_Workflow.md
|
||||
- intro/Working_With_Volumes.md
|
||||
- intro/Compose_For_Dev_Stacks.md
|
||||
- intro/Docker_Machine.md
|
||||
- - intro/Advanced_Dockerfiles.md
|
||||
- intro/Application_Configuration.md
|
||||
- intro/Logging.md
|
||||
- intro/Resource_Limits.md
|
||||
- - intro/Namespaces_Cgroups.md
|
||||
- intro/Copy_On_Write.md
|
||||
#- intro/Containers_From_Scratch.md
|
||||
- - intro/Container_Engines.md
|
||||
- intro/Ecosystem.md
|
||||
- intro/Orchestration_Overview.md
|
||||
- common/thankyou.md
|
||||
- intro/links.md
|
||||
|
||||
@@ -312,7 +312,7 @@ CMD gunicorn --bind 0.0.0.0:5000 --workers 10 counter:app
|
||||
EXPOSE 5000
|
||||
```
|
||||
|
||||
(Source: [trainingwheels Dockerfile](https://github.com/jpetazzo/trainingwheels/blob/master/www/Dockerfile))
|
||||
(Source: [traininghweels Dockerfile](https://github.com/jpetazzo/trainingwheels/blob/master/www/Dockerfile))
|
||||
|
||||
---
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
# Accessing internal services
|
||||
|
||||
- When we are logged in on a cluster node, we can access internal services
|
||||
|
||||
(by virtue of the Kubernetes network model: all nodes can reach all pods and services)
|
||||
|
||||
- When we are accessing a remote cluster, things are different
|
||||
|
||||
(generally, our local machine won't have access to the cluster's internal subnet)
|
||||
|
||||
- How can we temporarily access a service without exposing it to everyone?
|
||||
|
||||
--
|
||||
|
||||
- `kubectl proxy`: gives us access to the API, which includes a proxy for HTTP resources
|
||||
|
||||
- `kubectl port-forward`: allows forwarding of TCP ports to arbitrary pods, services, ...
|
||||
|
||||
---
|
||||
|
||||
## Suspension of disbelief
|
||||
|
||||
The exercises in this section assume that we have set up `kubectl` on our
|
||||
local machine in order to access a remote cluster.
|
||||
|
||||
We will therefore show how to access services and pods of the remote cluster,
|
||||
from our local machine.
|
||||
|
||||
You can also run these exercises directly on the cluster (if you haven't
|
||||
installed and set up `kubectl` locally).
|
||||
|
||||
Running commands locally will be less useful
|
||||
(since you could access services and pods directly),
|
||||
but keep in mind that these commands will work anywhere as long as you have
|
||||
installed and set up `kubectl` to communicate with your cluster.
|
||||
|
||||
---
|
||||
|
||||
## `kubectl proxy` in theory
|
||||
|
||||
- Running `kubectl proxy` gives us access to the entire Kubernetes API
|
||||
|
||||
- The API includes routes to proxy HTTP traffic
|
||||
|
||||
- These routes look like the following:
|
||||
|
||||
`/api/v1/namespaces/<namespace>/services/<service>/proxy`
|
||||
|
||||
- We just add the URI to the end of the request, for instance:
|
||||
|
||||
`/api/v1/namespaces/<namespace>/services/<service>/proxy/index.html`
|
||||
|
||||
- We can access `services` and `pods` this way
|
||||
|
||||
---
|
||||
|
||||
## `kubectl proxy` in practice
|
||||
|
||||
- Let's access the `webui` service through `kubectl proxy`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Run an API proxy in the background:
|
||||
```bash
|
||||
kubectl proxy &
|
||||
```
|
||||
|
||||
- Access the `webui` service:
|
||||
```bash
|
||||
curl localhost:8001/api/v1/namespaces/default/services/webui/proxy/index.html
|
||||
```
|
||||
|
||||
- Terminate the proxy:
|
||||
```bash
|
||||
kill %1
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## `kubectl port-forward` in theory
|
||||
|
||||
- What if we want to access a TCP service?
|
||||
|
||||
- We can use `kubectl port-forward` instead
|
||||
|
||||
- It will create a TCP relay to forward connections to a specific port
|
||||
|
||||
(of a pod, service, deployment...)
|
||||
|
||||
- The syntax is:
|
||||
|
||||
`kubectl port-forward service/name_of_service local_port:remote_port`
|
||||
|
||||
- If only one port number is specified, it is used for both local and remote ports
|
||||
|
||||
---
|
||||
|
||||
## `kubectl port-forward` in practice
|
||||
|
||||
- Let's access our remote Redis server
|
||||
|
||||
.exercise[
|
||||
|
||||
- Forward connections from local port 10000 to remote port 6379:
|
||||
```bash
|
||||
kubectl port-forward svc/redis 10000:6379 &
|
||||
```
|
||||
|
||||
- Connect to the Redis server:
|
||||
```bash
|
||||
telnet localhost 10000
|
||||
```
|
||||
|
||||
- Issue a few commands, e.g. `INFO server` then `QUIT`
|
||||
|
||||
<!--
|
||||
```wait Connected to localhost```
|
||||
```keys INFO server```
|
||||
```keys ^J```
|
||||
```keys QUIT```
|
||||
```keys ^J```
|
||||
-->
|
||||
|
||||
- Terminate the port forwarder:
|
||||
```bash
|
||||
kill %1
|
||||
```
|
||||
|
||||
]
|
||||
@@ -1,533 +0,0 @@
|
||||
# Authentication and authorization
|
||||
|
||||
*And first, a little refresher!*
|
||||
|
||||
- Authentication = verifying the identity of a person
|
||||
|
||||
On a UNIX system, we can authenticate with login+password, SSH keys ...
|
||||
|
||||
- Authorization = listing what they are allowed to do
|
||||
|
||||
On a UNIX system, this can include file permissions, sudoer entries ...
|
||||
|
||||
- Sometimes abbreviated as "authn" and "authz"
|
||||
|
||||
- In good modular systems, these things are decoupled
|
||||
|
||||
(so we can e.g. change a password or SSH key without having to reset access rights)
|
||||
|
||||
---
|
||||
|
||||
## Authentication in Kubernetes
|
||||
|
||||
- When the API server receives a request, it tries to authenticate it
|
||||
|
||||
(it examines headers, certificates ... anything available)
|
||||
|
||||
- Many authentication methods can be used simultaneously:
|
||||
|
||||
- TLS client certificates (that's what we've been doing with `kubectl` so far)
|
||||
|
||||
- bearer tokens (a secret token in the HTTP headers of the request)
|
||||
|
||||
- [HTTP basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication) (carrying user and password in a HTTP header)
|
||||
|
||||
- authentication proxy (sitting in front of the API and setting trusted headers)
|
||||
|
||||
- It's the job of the authentication method to produce:
|
||||
|
||||
- the user name
|
||||
- the user ID
|
||||
- a list of groups
|
||||
|
||||
- The API server doesn't interpret these; it'll be the job of *authorizers*
|
||||
|
||||
---
|
||||
|
||||
## Anonymous requests
|
||||
|
||||
- If any authentication method *rejects* a request, it's denied
|
||||
|
||||
(`401 Unauthorized` HTTP code)
|
||||
|
||||
- If a request is neither accepted nor accepted by anyone, it's anonymous
|
||||
|
||||
- the user name is `system:anonymous`
|
||||
|
||||
- the list of groups is `[system:unauthenticated]`
|
||||
|
||||
- By default, the anonymous user can't do anything
|
||||
|
||||
(that's what you get if you just `curl` the Kubernetes API)
|
||||
|
||||
---
|
||||
|
||||
## Authentication with TLS certificates
|
||||
|
||||
- This is enabled in most Kubernetes deployments
|
||||
|
||||
- The user name is derived from the `CN` in the client certificates
|
||||
|
||||
- The groups are derived from the `O` fields in the client certificate
|
||||
|
||||
- From the point of view of the Kubernetes API, users do not exist
|
||||
|
||||
(i.e. they are not stored in etcd or anywhere else)
|
||||
|
||||
- Users can be created (and given membership to groups) independently of the API
|
||||
|
||||
- The Kubernetes API can be set up to use your custom CA to validate client certs
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Viewing our admin certificate
|
||||
|
||||
- Let's inspect the certificate we've been using all this time!
|
||||
|
||||
.exercise[
|
||||
|
||||
- This command will show the `CN` and `O` fields for our certificate:
|
||||
```bash
|
||||
kubectl config view \
|
||||
--raw \
|
||||
-o json \
|
||||
| jq -r .users[0].user[\"client-certificate-data\"] \
|
||||
| base64 -d \
|
||||
| openssl x509 -text \
|
||||
| grep Subject:
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Let's break down that command together! 😅
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Breaking down the command
|
||||
|
||||
- `kubectl config view` shows the Kubernetes user configuration
|
||||
- `--raw` includes certificate information (which shows as REDACTED otherwise)
|
||||
- `-o json` outputs the information in JSON format
|
||||
- `| jq ...` extracts the field with the user certificate (in base64)
|
||||
- `| base64 -d` decodes the base64 format (now we have a PEM file)
|
||||
- `| openssl x509 -text` parses the certificate and outputs it as plain text
|
||||
- `| grep Subject:` shows us the line that interests us
|
||||
|
||||
→ We are user `kubernetes-admin`, in group `system:masters`.
|
||||
|
||||
---
|
||||
|
||||
## Authentication with tokens
|
||||
|
||||
- Tokens are passed as HTTP headers:
|
||||
|
||||
`Authorization: Bearer and-then-here-comes-the-token`
|
||||
|
||||
- Tokens can be validated through a number of different methods:
|
||||
|
||||
- static tokens hard-coded in a file on the API server
|
||||
|
||||
- [bootstrap tokens](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/) (special case to create a cluster or join nodes)
|
||||
|
||||
- [OpenID Connect tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) (to delegate authentication to compatible OAuth2 providers)
|
||||
|
||||
- service accounts (these deserve more details, coming right up!)
|
||||
|
||||
---
|
||||
|
||||
## Service accounts
|
||||
|
||||
- A service account is a user that exists in the Kubernetes API
|
||||
|
||||
(it is visible with e.g. `kubectl get serviceaccounts`)
|
||||
|
||||
- Service accounts can therefore be created / updated dynamically
|
||||
|
||||
(they don't require hand-editing a file and restarting the API server)
|
||||
|
||||
- A service account is associated with a set of secrets
|
||||
|
||||
(the kind that you can view with `kubectl get secrets`)
|
||||
|
||||
- Service accounts are generally used to grant permissions to applications, services ...
|
||||
|
||||
(as opposed to humans)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Token authentication in practice
|
||||
|
||||
- We are going to list existing service accounts
|
||||
|
||||
- Then we will extract the token for a given service account
|
||||
|
||||
- And we will use that token to authenticate with the API
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Listing service accounts
|
||||
|
||||
.exercise[
|
||||
|
||||
- The resource name is `serviceaccount` or `sa` in short:
|
||||
```bash
|
||||
kubectl get sa
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
There should be just one service account in the default namespace: `default`.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Finding the secret
|
||||
|
||||
.exercise[
|
||||
|
||||
- List the secrets for the `default` service account:
|
||||
```bash
|
||||
kubectl get sa default -o yaml
|
||||
SECRET=$(kubectl get sa default -o json | jq -r .secrets[0].name)
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
It should be named `default-token-XXXXX`.
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Extracting the token
|
||||
|
||||
- The token is stored in the secret, wrapped with base64 encoding
|
||||
|
||||
.exercise[
|
||||
|
||||
- View the secret:
|
||||
```bash
|
||||
kubectl get secret $SECRET -o yaml
|
||||
```
|
||||
|
||||
- Extract the token and decode it:
|
||||
```bash
|
||||
TOKEN=$(kubectl get secret $SECRET -o json \
|
||||
| jq -r .data.token | base64 -d)
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Using the token
|
||||
|
||||
- Let's send a request to the API, without and with the token
|
||||
|
||||
.exercise[
|
||||
|
||||
- Find the ClusterIP for the `kubernetes` service:
|
||||
```bash
|
||||
kubectl get svc kubernetes
|
||||
API=$(kubectl get svc kubernetes -o json | jq -r .spec.clusterIP)
|
||||
```
|
||||
|
||||
- Connect without the token:
|
||||
```bash
|
||||
curl -k https://$API
|
||||
```
|
||||
|
||||
- Connect with the token:
|
||||
```bash
|
||||
curl -k -H "Authorization: Bearer $TOKEN" https://$API
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Results
|
||||
|
||||
- In both cases, we will get a "Forbidden" error
|
||||
|
||||
- Without authentication, the user is `system:anonymous`
|
||||
|
||||
- With authentication, it is shown as `system:serviceaccount:default:default`
|
||||
|
||||
- The API "sees" us as a different user
|
||||
|
||||
- But neither user has any right, so we can't do nothin'
|
||||
|
||||
- Let's change that!
|
||||
|
||||
---
|
||||
|
||||
## Authorization in Kubernetes
|
||||
|
||||
- There are multiple ways to grant permissions in Kubernetes, called [authorizers](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#authorization-modules):
|
||||
|
||||
- [Node Authorization](https://kubernetes.io/docs/reference/access-authn-authz/node/) (used internally by kubelet; we can ignore it)
|
||||
|
||||
- [Attribute-based access control](https://kubernetes.io/docs/reference/access-authn-authz/abac/) (powerful but complex and static; ignore it too)
|
||||
|
||||
- [Webhook](https://kubernetes.io/docs/reference/access-authn-authz/webhook/) (each API request is submitted to an external service for approval)
|
||||
|
||||
- [Role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) (associates permissions to users dynamically)
|
||||
|
||||
- The one we want is the last one, generally abbreviated as RBAC
|
||||
|
||||
---
|
||||
|
||||
## Role-based access control
|
||||
|
||||
- RBAC allows to specify fine-grained permissions
|
||||
|
||||
- Permissions are expressed as *rules*
|
||||
|
||||
- A rule is a combination of:
|
||||
|
||||
- [verbs](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb) like create, get, list, update, delete ...
|
||||
|
||||
- resources (as in "API resource", like pods, nodes, services ...)
|
||||
|
||||
- resource names (to specify e.g. one specific pod instead of all pods)
|
||||
|
||||
- in some case, [subresources](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-resources) (e.g. logs are subresources of pods)
|
||||
|
||||
---
|
||||
|
||||
## From rules to roles to rolebindings
|
||||
|
||||
- A *role* is an API object containing a list of *rules*
|
||||
|
||||
Example: role "external-load-balancer-configurator" can:
|
||||
- [list, get] resources [endpoints, services, pods]
|
||||
- [update] resources [services]
|
||||
|
||||
- A *rolebinding* associates a role with a user
|
||||
|
||||
Example: rolebinding "external-load-balancer-configurator":
|
||||
- associates user "external-load-balancer-configurator"
|
||||
- with role "external-load-balancer-configurator"
|
||||
|
||||
- Yes, there can be users, roles, and rolebindings with the same name
|
||||
|
||||
- It's a good idea for 1-1-1 bindings; not so much for 1-N ones
|
||||
|
||||
---
|
||||
|
||||
## Cluster-scope permissions
|
||||
|
||||
- API resources Role and RoleBinding are for objects within a namespace
|
||||
|
||||
- We can also define API resources ClusterRole and ClusterRoleBinding
|
||||
|
||||
- These are a superset, allowing to:
|
||||
|
||||
- specify actions on cluster-wide objects (like nodes)
|
||||
|
||||
- operate across all namespaces
|
||||
|
||||
- We can create Role and RoleBinding resources within a namespaces
|
||||
|
||||
- ClusterRole and ClusterRoleBinding resources are global
|
||||
|
||||
---
|
||||
|
||||
## Pods and service accounts
|
||||
|
||||
- A pod can be associated to a service account
|
||||
|
||||
- by default, it is associated to the `default` service account
|
||||
|
||||
- as we've seen earlier, this service account has no permission anyway
|
||||
|
||||
- The associated token is exposed into the pod's filesystem
|
||||
|
||||
(in `/var/run/secrets/kubernetes.io/serviceaccount/token`)
|
||||
|
||||
- Standard Kubernetes tooling (like `kubectl`) will look for it there
|
||||
|
||||
- So Kubernetes tools running in a pod will automatically use the service account
|
||||
|
||||
---
|
||||
|
||||
## In practice
|
||||
|
||||
- We are going to create a service account
|
||||
|
||||
- We will use an existing cluster role (`view`)
|
||||
|
||||
- We will bind together this role and this service account
|
||||
|
||||
- Then we will run a pod using that service account
|
||||
|
||||
- In this pod, we will install `kubectl` and check our permissions
|
||||
|
||||
---
|
||||
|
||||
## Creating a service account
|
||||
|
||||
- We will call the new service account `viewer`
|
||||
|
||||
(note that nothing prevents us from calling it `view`, like the role)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the new service account:
|
||||
```bash
|
||||
kubectl create serviceaccount viewer
|
||||
```
|
||||
|
||||
- List service accounts now:
|
||||
```bash
|
||||
kubectl get serviceaccounts
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Binding a role to the service account
|
||||
|
||||
- Binding a role = creating a *rolebinding* object
|
||||
|
||||
- We will call that object `viewercanview`
|
||||
|
||||
(but again, we could call it `view`)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the new role binding:
|
||||
```bash
|
||||
kubectl create rolebinding viewercanview \
|
||||
--clusterrole=view \
|
||||
--serviceaccount=default:viewer
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
It's important to note a couple of details in these flags ...
|
||||
|
||||
---
|
||||
|
||||
## Roles vs Cluster Roles
|
||||
|
||||
- We used `--clusterrole=view`
|
||||
|
||||
- What would have happened if we had used `--role=view`?
|
||||
|
||||
- we would have bound the role `view` from the local namespace
|
||||
<br/>(instead of the cluster role `view`)
|
||||
|
||||
- the command would have worked fine (no error)
|
||||
|
||||
- but later, our API requests would have been denied
|
||||
|
||||
- This is a deliberate design decision
|
||||
|
||||
(we can reference roles that don't exist, and create/update them later)
|
||||
|
||||
---
|
||||
|
||||
## Users vs Service Accounts
|
||||
|
||||
- We used `--serviceaccount=default:viewer`
|
||||
|
||||
- What would have happened if we had used `--user=default:viewer`?
|
||||
|
||||
- we would have bound the role to a user instead of a service account
|
||||
|
||||
- again, the command would have worked fine (no error)
|
||||
|
||||
- ... but our API requests would have been denied later
|
||||
|
||||
- What's about the `default:` prefix?
|
||||
|
||||
- that's the namespace of the service account
|
||||
|
||||
- yes, it could be inferred from context, but ... `kubectl` requires it
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
- We will run an `alpine` pod and install `kubectl` there
|
||||
|
||||
.exercise[
|
||||
|
||||
- Run a one-time pod:
|
||||
```bash
|
||||
kubectl run eyepod --rm -ti --restart=Never \
|
||||
--serviceaccount=viewer \
|
||||
--image alpine
|
||||
```
|
||||
|
||||
- Install `curl`, then use it to install `kubectl`:
|
||||
```bash
|
||||
apk add --no-cache curl
|
||||
URLBASE=https://storage.googleapis.com/kubernetes-release/release
|
||||
KUBEVER=$(curl -s $URLBASE/stable.txt)
|
||||
curl -LO $URLBASE/$KUBEVER/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Running `kubectl` in the pod
|
||||
|
||||
- We'll try to use our `view` permissions, then to create an object
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check that we can, indeed, view things:
|
||||
```bash
|
||||
./kubectl get all
|
||||
```
|
||||
|
||||
- But that we can't create things:
|
||||
```
|
||||
./kubectl run tryme --image=nginx
|
||||
```
|
||||
|
||||
- Exit the container with `exit` or `^D`
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Testing directly with `kubectl`
|
||||
|
||||
- We can also check for permission with `kubectl auth can-i`:
|
||||
```bash
|
||||
kubectl auth can-i list nodes
|
||||
kubectl auth can-i create pods
|
||||
kubectl auth can-i get pod/name-of-pod
|
||||
kubectl auth can-i get /url-fragment-of-api-request/
|
||||
kubectl auth can-i '*' services
|
||||
```
|
||||
|
||||
- And we can check permissions on behalf of other users:
|
||||
```bash
|
||||
kubectl auth can-i list nodes \
|
||||
--as some-user
|
||||
kubectl auth can-i list nodes \
|
||||
--as system:serviceaccount:<namespace>:<name-of-service-account>
|
||||
```
|
||||
@@ -1,161 +0,0 @@
|
||||
# Building images with the Docker Engine
|
||||
|
||||
- Until now, we have built our images manually, directly on a node
|
||||
|
||||
- We are going to show how to build images from within the cluster
|
||||
|
||||
(by executing code in a container controlled by Kubernetes)
|
||||
|
||||
- We are going to use the Docker Engine for that purpose
|
||||
|
||||
- To access the Docker Engine, we will mount the Docker socket in our container
|
||||
|
||||
- After building the image, we will push it to our self-hosted registry
|
||||
|
||||
---
|
||||
|
||||
## Resource specification for our builder pod
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: build-image
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: docker-build
|
||||
image: docker
|
||||
env:
|
||||
- name: REGISTRY_PORT
|
||||
value: "`3XXXX`"
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
apk add --no-cache git &&
|
||||
mkdir /workspace &&
|
||||
git clone https://github.com/jpetazzo/container.training /workspace &&
|
||||
docker build -t localhost:$REGISTRY_PORT/worker /workspace/dockercoins/worker &&
|
||||
docker push localhost:$REGISTRY_PORT/worker
|
||||
volumeMounts:
|
||||
- name: docker-socket
|
||||
mountPath: /var/run/docker.sock
|
||||
volumes:
|
||||
- name: docker-socket
|
||||
hostPath:
|
||||
path: /var/run/docker.sock
|
||||
```
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Breaking down the pod specification (1/2)
|
||||
|
||||
- `restartPolicy: OnFailure` prevents the build from running in an infinite lopo
|
||||
|
||||
- We use the `docker` image (so that the `docker` CLI is available)
|
||||
|
||||
- We rely on the fact that the `docker` image is based on `alpine`
|
||||
|
||||
(which is why we use `apk` to install `git`)
|
||||
|
||||
- The port for the registry is passed through an environment variable
|
||||
|
||||
(this avoids repeating it in the specification, which would be error-prone)
|
||||
|
||||
.warning[The environment variable has to be a string, so the `"`s are mandatory!]
|
||||
|
||||
---
|
||||
|
||||
## Breaking down the pod specification (2/2)
|
||||
|
||||
- The volume `docker-socket` is declared with a `hostPath`, indicating a bind-mount
|
||||
|
||||
- It is then mounted in the container onto the default Docker socket path
|
||||
|
||||
- We show a interesting way to specify the commands to run in the container:
|
||||
|
||||
- the command executed will be `sh -c <args>`
|
||||
|
||||
- `args` is a list of strings
|
||||
|
||||
- `|` is used to pass a multi-line string in the YAML file
|
||||
|
||||
---
|
||||
|
||||
## Running our pod
|
||||
|
||||
- Let's try this out!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the port used by our self-hosted registry:
|
||||
```bash
|
||||
kubectl get svc registry
|
||||
```
|
||||
|
||||
- Edit `~/container.training/k8s/docker-build.yaml` to put the port number
|
||||
|
||||
- Schedule the pod by applying the resource file:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/docker-build.yaml
|
||||
```
|
||||
|
||||
- Watch the logs:
|
||||
```bash
|
||||
stern build-image
|
||||
```
|
||||
|
||||
<!--
|
||||
```longwait latest: digest: sha256:```
|
||||
```keys ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## What's missing?
|
||||
|
||||
What do we need to change to make this production-ready?
|
||||
|
||||
- Build from a long-running container (e.g. a `Deployment`) triggered by web hooks
|
||||
|
||||
(the payload of the web hook could indicate the repository to build)
|
||||
|
||||
- Build a specific branch or tag; tag image accordingly
|
||||
|
||||
- Handle repositories where the Dockerfile is not at the root
|
||||
|
||||
(or containing multiple Dockerfiles)
|
||||
|
||||
- Expose build logs so that troubleshooting is straightforward
|
||||
|
||||
--
|
||||
|
||||
🤔 That seems like a lot of work!
|
||||
|
||||
--
|
||||
|
||||
That's why services like Docker Hub (with [automated builds](https://docs.docker.com/docker-hub/builds/)) are helpful.
|
||||
<br/>
|
||||
They handle the whole "code repository → Docker image" workflow.
|
||||
|
||||
---
|
||||
|
||||
## Things to be aware of
|
||||
|
||||
- This is talking directly to a node's Docker Engine to build images
|
||||
|
||||
- It bypasses resource allocation mechanisms used by Kubernetes
|
||||
|
||||
(but you can use *taints* and *tolerations* to dedicate builder nodes)
|
||||
|
||||
- Be careful not to introduce conflicts when naming images
|
||||
|
||||
(e.g. do not allow the user to specify the image names!)
|
||||
|
||||
- Your builds are going to be *fast*
|
||||
|
||||
(because they will leverage Docker's caching system)
|
||||
@@ -1,218 +0,0 @@
|
||||
# Building images with Kaniko
|
||||
|
||||
- [Kaniko](https://github.com/GoogleContainerTools/kaniko) is an open source tool to build container images within Kubernetes
|
||||
|
||||
- It can build an image using any standard Dockerfile
|
||||
|
||||
- The resulting image can be pushed to a registry or exported as a tarball
|
||||
|
||||
- It doesn't require any particular privilege
|
||||
|
||||
(and can therefore run in a regular container in a regular pod)
|
||||
|
||||
- This combination of features is pretty unique
|
||||
|
||||
(most other tools use different formats, or require elevated privileges)
|
||||
|
||||
---
|
||||
|
||||
## Kaniko in practice
|
||||
|
||||
- Kaniko provides an "executor image", `gcr.io/kaniko-project/executor`
|
||||
|
||||
- When running that image, we need to specify at least:
|
||||
|
||||
- the path to the build context (=the directory with our Dockerfile)
|
||||
|
||||
- the target image name (including the registry address)
|
||||
|
||||
- Simplified example:
|
||||
```
|
||||
docker run \
|
||||
-v ...:/workspace gcr.io/kaniko-project/executor \
|
||||
--context=/workspace \
|
||||
--destination=registry:5000/image_name:image_tag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running Kaniko in a Docker container
|
||||
|
||||
- Let's build the image for the DockerCoins `worker` service with Kaniko
|
||||
|
||||
.exercise[
|
||||
|
||||
- Find the port number for our self-hosted registry:
|
||||
```bash
|
||||
kubectl get svc registry
|
||||
PORT=$(kubectl get svc registry -o json | jq .spec.ports[0].nodePort)
|
||||
```
|
||||
|
||||
- Run Kaniko:
|
||||
```bash
|
||||
docker run --net host \
|
||||
-v ~/container.training/dockercoins/worker:/workspace \
|
||||
gcr.io/kaniko-project/executor \
|
||||
--context=/workspace \
|
||||
--destination=127.0.0.1:$PORT/worker-kaniko:latest
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We use `--net host` so that we can connect to the registry over `127.0.0.1`.
|
||||
|
||||
---
|
||||
|
||||
## Running Kaniko in a Kubernetes pod
|
||||
|
||||
- We need to mount or copy the build context to the pod
|
||||
|
||||
- We are going to build straight from the git repository
|
||||
|
||||
(to avoid depending on files sitting on a node, outside of containers)
|
||||
|
||||
- We need to `git clone` the repository before running Kaniko
|
||||
|
||||
- We are going to use two containers sharing a volume:
|
||||
|
||||
- a first container to `git clone` the repository to the volume
|
||||
|
||||
- a second container to run Kaniko, using the content of the volume
|
||||
|
||||
- However, we need the first container to be done before running the second one
|
||||
|
||||
🤔 How could we do that?
|
||||
|
||||
---
|
||||
|
||||
## [Init Containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) to the rescue
|
||||
|
||||
- A pod can have a list of `initContainers`
|
||||
|
||||
- `initContainers` are executed in the specified order
|
||||
|
||||
- Each Init Container needs to complete (exit) successfully
|
||||
|
||||
- If any Init Container fails (non-zero exit status) the pod fails
|
||||
|
||||
(what happens next depends on the pod's `restartPolicy`)
|
||||
|
||||
- After all Init Containers have run successfully, normal `containers` are started
|
||||
|
||||
- We are going to execute the `git clone` operation in an Init Container
|
||||
|
||||
---
|
||||
|
||||
## Our Kaniko builder pod
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: kaniko-build
|
||||
spec:
|
||||
initContainers:
|
||||
- name: git-clone
|
||||
image: alpine
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
apk add --no-cache git &&
|
||||
git clone git://github.com/jpetazzo/container.training /workspace
|
||||
volumeMounts:
|
||||
- name: workspace
|
||||
mountPath: /workspace
|
||||
containers:
|
||||
- name: build-image
|
||||
image: gcr.io/kaniko-project/executor:latest
|
||||
args:
|
||||
- "--context=/workspace/dockercoins/rng"
|
||||
- "--insecure"
|
||||
- "--destination=registry:5000/rng-kaniko:latest"
|
||||
volumeMounts:
|
||||
- name: workspace
|
||||
mountPath: /workspace
|
||||
volumes:
|
||||
- name: workspace
|
||||
```
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Explanations
|
||||
|
||||
- We define a volume named `workspace` (using the default `emptyDir` provider)
|
||||
|
||||
- That volume is mounted to `/workspace` in both our containers
|
||||
|
||||
- The `git-clone` Init Container installs `git` and runs `git clone`
|
||||
|
||||
- The `build-image` container executes Kaniko
|
||||
|
||||
- We use our self-hosted registry DNS name (`registry`)
|
||||
|
||||
- We add `--insecure` to use plain HTTP to talk to the registry
|
||||
|
||||
---
|
||||
|
||||
## Running our Kaniko builder pod
|
||||
|
||||
- The YAML for the pod is in `k8s/kaniko-build.yaml`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the pod:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/kaniko-build.yaml
|
||||
```
|
||||
|
||||
- Watch the logs:
|
||||
```bash
|
||||
stern kaniko
|
||||
```
|
||||
|
||||
<!--
|
||||
```longwait registry:5000/rng-kaniko:latest:```
|
||||
```keys ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Discussion
|
||||
|
||||
*What should we use? The Docker build technique shown earlier? Kaniko? Something else?*
|
||||
|
||||
- The Docker build technique is simple, and has the potential to be very fast
|
||||
|
||||
- However, it doesn't play nice with Kubernetes resource limits
|
||||
|
||||
- Kaniko plays nice with resource limits
|
||||
|
||||
- However, it's slower (there is no caching at all)
|
||||
|
||||
- The ultimate building tool will probably be [Jessica Frazelle](https://twitter.com/jessfraz)'s [img](https://github.com/genuinetools/img) builder
|
||||
|
||||
(it depends on upstream changes that are not in Kubernetes 1.11.2 yet)
|
||||
|
||||
But ... is it all about [speed](https://github.com/AkihiroSuda/buildbench/issues/1)? (No!)
|
||||
|
||||
---
|
||||
|
||||
## The big picture
|
||||
|
||||
- For starters: the [Docker Hub automated builds](https://docs.docker.com/docker-hub/builds/) are very easy to set up
|
||||
|
||||
- link a GitHub repository with the Docker Hub
|
||||
|
||||
- each time you push to GitHub, an image gets build on the Docker Hub
|
||||
|
||||
- If this doesn't work for you: why?
|
||||
|
||||
- too slow (I'm far from `us-east-1`!) → consider using your cloud provider's registry
|
||||
|
||||
- I'm not using a cloud provider → ok, perhaps you need to self-host then
|
||||
|
||||
- I need fancy features (e.g. CI) → consider something like GitLab
|
||||
@@ -1,542 +0,0 @@
|
||||
# Managing configuration
|
||||
|
||||
- Some applications need to be configured (obviously!)
|
||||
|
||||
- There are many ways for our code to pick up configuration:
|
||||
|
||||
- command-line arguments
|
||||
|
||||
- environment variables
|
||||
|
||||
- configuration files
|
||||
|
||||
- configuration servers (getting configuration from a database, an API...)
|
||||
|
||||
- ... and more (because programmers can be very creative!)
|
||||
|
||||
- How can we do these things with containers and Kubernetes?
|
||||
|
||||
---
|
||||
|
||||
## Passing configuration to containers
|
||||
|
||||
- There are many ways to pass configuration to code running in a container:
|
||||
|
||||
- baking it in a custom image
|
||||
|
||||
- command-line arguments
|
||||
|
||||
- environment variables
|
||||
|
||||
- injecting configuration files
|
||||
|
||||
- exposing it over the Kubernetes API
|
||||
|
||||
- configuration servers
|
||||
|
||||
- Let's review these different strategies!
|
||||
|
||||
---
|
||||
|
||||
## Baking custom images
|
||||
|
||||
- Put the configuration in the image
|
||||
|
||||
(it can be in a configuration file, but also `ENV` or `CMD` actions)
|
||||
|
||||
- It's easy! It's simple!
|
||||
|
||||
- Unfortunately, it also has downsides:
|
||||
|
||||
- multiplication of images
|
||||
|
||||
- different images for dev, staging, prod ...
|
||||
|
||||
- minor reconfigurations require a whole build/push/pull cycle
|
||||
|
||||
- Avoid doing it unless you don't have the time to figure out other options
|
||||
|
||||
---
|
||||
|
||||
## Command-line arguments
|
||||
|
||||
- Pass options to `args` array in the container specification
|
||||
|
||||
- Example ([source](https://github.com/coreos/pods/blob/master/kubernetes.yaml#L29)):
|
||||
```yaml
|
||||
args:
|
||||
- "--data-dir=/var/lib/etcd"
|
||||
- "--advertise-client-urls=http://127.0.0.1:2379"
|
||||
- "--listen-client-urls=http://127.0.0.1:2379"
|
||||
- "--listen-peer-urls=http://127.0.0.1:2380"
|
||||
- "--name=etcd"
|
||||
```
|
||||
|
||||
- The options can be passed directly to the program that we run ...
|
||||
|
||||
... or to a wrapper script that will use them to e.g. generate a config file
|
||||
|
||||
---
|
||||
|
||||
## Command-line arguments, pros & cons
|
||||
|
||||
- Works great when options are passed directly to the running program
|
||||
|
||||
(otherwise, a wrapper script can work around the issue)
|
||||
|
||||
- Works great when there aren't too many parameters
|
||||
|
||||
(to avoid a 20-lines `args` array)
|
||||
|
||||
- Requires documentation and/or understanding of the underlying program
|
||||
|
||||
("which parameters and flags do I need, again?")
|
||||
|
||||
- Well-suited for mandatory parameters (without default values)
|
||||
|
||||
- Not ideal when we need to pass a real configuration file anyway
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
- Pass options through the `env` map in the container specification
|
||||
|
||||
- Example:
|
||||
```yaml
|
||||
env:
|
||||
- name: ADMIN_PORT
|
||||
value: "8080"
|
||||
- name: ADMIN_AUTH
|
||||
value: Basic
|
||||
- name: ADMIN_CRED
|
||||
value: "admin:0pensesame!"
|
||||
```
|
||||
|
||||
.warning[`value` must be a string! Make sure that numbers and fancy strings are quoted.]
|
||||
|
||||
🤔 Why this weird `{name: xxx, value: yyy}` scheme? It will be revealed soon!
|
||||
|
||||
---
|
||||
|
||||
## The downward API
|
||||
|
||||
- In the previous example, environment variables have fixed values
|
||||
|
||||
- We can also use a mechanism called the *downward API*
|
||||
|
||||
- The downward API allows to expose pod or container information
|
||||
|
||||
- either through special files (we won't show that for now)
|
||||
|
||||
- or through environment variables
|
||||
|
||||
- The value of these environment variables is computed when the container is started
|
||||
|
||||
- Remember: environment variables won't (can't) change after container start
|
||||
|
||||
- Let's see a few concrete examples!
|
||||
|
||||
---
|
||||
|
||||
## Exposing the pod's namespace
|
||||
|
||||
```yaml
|
||||
- name: MY_POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
```
|
||||
|
||||
- Useful to generate FQDN of services
|
||||
|
||||
(in some contexts, a short name is not enough)
|
||||
|
||||
- For instance, the two commands should be equivalent:
|
||||
```
|
||||
curl api-backend
|
||||
curl api-backend.$MY_POD_NAMESPACE.svc.cluster.local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exposing the pod's IP address
|
||||
|
||||
```yaml
|
||||
- name: MY_POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
```
|
||||
|
||||
- Useful if we need to know our IP address
|
||||
|
||||
(we could also read it from `eth0`, but this is more solid)
|
||||
|
||||
---
|
||||
|
||||
## Exposing the container's resource limits
|
||||
|
||||
```yaml
|
||||
- name: MY_MEM_LIMIT
|
||||
valueFrom:
|
||||
resourceFieldRef:
|
||||
containerName: test-container
|
||||
resource: limits.memory
|
||||
```
|
||||
|
||||
- Useful for runtimes where memory is garbage collected
|
||||
|
||||
- Example: the JVM
|
||||
|
||||
(the memory available to the JVM should be set with the `-Xmx ` flag)
|
||||
|
||||
- Best practice: set a memory limit, and pass it to the runtime
|
||||
|
||||
(see [this blog post](https://very-serio.us/2017/12/05/running-jvms-in-kubernetes/) for a detailed example)
|
||||
|
||||
---
|
||||
|
||||
## More about the downward API
|
||||
|
||||
- [This documentation page](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/) tells more about these environment variables
|
||||
|
||||
- And [this one](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/) explains the other way to use the downward API
|
||||
|
||||
(through files that get created in the container filesystem)
|
||||
|
||||
---
|
||||
|
||||
## Environment variables, pros and cons
|
||||
|
||||
- Works great when the running program expects these variables
|
||||
|
||||
- Works great for optional parameters with reasonable defaults
|
||||
|
||||
(since the container image can provide these defaults)
|
||||
|
||||
- Sort of auto-documented
|
||||
|
||||
(we can see which environment variables are defined in the image, and their values)
|
||||
|
||||
- Can be (ab)used with longer values ...
|
||||
|
||||
- ... You *can* put an entire Tomcat configuration file in an environment ...
|
||||
|
||||
- ... But *should* you?
|
||||
|
||||
(Do it if you really need to, we're not judging! But we'll see better ways.)
|
||||
|
||||
---
|
||||
|
||||
## Injecting configuration files
|
||||
|
||||
- Sometimes, there is no way around it: we need to inject a full config file
|
||||
|
||||
- Kubernetes provides a mechanism for that purpose: `configmaps`
|
||||
|
||||
- A configmap is a Kubernetes resource that exists in a namespace
|
||||
|
||||
- Conceptually, it's a key/value map
|
||||
|
||||
(values are arbitrary strings)
|
||||
|
||||
- We can think about them in (at least) two different ways:
|
||||
|
||||
- as holding entire configuration file(s)
|
||||
|
||||
- as holding individual configuration parameters
|
||||
|
||||
*Note: to hold sensitive information, we can use "Secrets", which
|
||||
are another type of resource behaving very much like configmaps.
|
||||
We'll cover them just after!*
|
||||
|
||||
---
|
||||
|
||||
## Configmaps storing entire files
|
||||
|
||||
- In this case, each key/value pair corresponds to a configuration file
|
||||
|
||||
- Key = name of the file
|
||||
|
||||
- Value = content of the file
|
||||
|
||||
- There can be one key/value pair, or as many as necessary
|
||||
|
||||
(for complex apps with multiple configuration files)
|
||||
|
||||
- Examples:
|
||||
```
|
||||
# Create a configmap with a single key, "app.conf"
|
||||
kubectl create configmap my-app-config --from-file=app.conf
|
||||
# Create a configmap with a single key, "app.conf" but another file
|
||||
kubectl create configmap my-app-config --from-file=app.conf=app-prod.conf
|
||||
# Create a configmap with multiple keys (one per file in the config.d directory)
|
||||
kubectl create configmap my-app-config --from-file=config.d/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configmaps storing individual parameters
|
||||
|
||||
- In this case, each key/value pair corresponds to a parameter
|
||||
|
||||
- Key = name of the parameter
|
||||
|
||||
- Value = value of the parameter
|
||||
|
||||
- Examples:
|
||||
```
|
||||
# Create a configmap with two keys
|
||||
kubectl create cm my-app-config \
|
||||
--from-literal=foreground=red \
|
||||
--from-literal=background=blue
|
||||
|
||||
# Create a configmap from a file containing key=val pairs
|
||||
kubectl create cm my-app-config \
|
||||
--from-env-file=app.conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Exposing configmaps to containers
|
||||
|
||||
- Configmaps can be exposed as plain files in the filesystem of a container
|
||||
|
||||
- this is achieved by declaring a volume and mounting it in the container
|
||||
|
||||
- this is particularly effective for configmaps containing whole files
|
||||
|
||||
- Configmaps can be exposed as environment variables in the container
|
||||
|
||||
- this is achieved with the downward API
|
||||
|
||||
- this is particularly effective for configmaps containing individual parameters
|
||||
|
||||
- Let's see how to do both!
|
||||
|
||||
---
|
||||
|
||||
## Passing a configuration file with a configmap
|
||||
|
||||
- We will start a load balancer powered by HAProxy
|
||||
|
||||
- We will use the [official `haproxy` image](https://hub.docker.com/_/haproxy/)
|
||||
|
||||
- It expects to find its configuration in `/usr/local/etc/haproxy/haproxy.cfg`
|
||||
|
||||
- We will provide a simple HAproxy configuration, `k8s/haproxy.cfg`
|
||||
|
||||
- It listens on port 80, and load balances connections between Google and Bing
|
||||
|
||||
---
|
||||
|
||||
## Creating the configmap
|
||||
|
||||
.exercise[
|
||||
|
||||
- Go to the `k8s` directory in the repository:
|
||||
```bash
|
||||
cd ~/container.training/k8s
|
||||
```
|
||||
|
||||
- Create a configmap named `haproxy` and holding the configuration file:
|
||||
```bash
|
||||
kubectl create configmap haproxy --from-file=haproxy.cfg
|
||||
```
|
||||
|
||||
- Check what our configmap looks like:
|
||||
```bash
|
||||
kubectl get configmap haproxy -o yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Using the configmap
|
||||
|
||||
We are going to use the following pod definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: haproxy
|
||||
spec:
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: haproxy
|
||||
containers:
|
||||
- name: haproxy
|
||||
image: haproxy
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /usr/local/etc/haproxy/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using the configmap
|
||||
|
||||
- The resource definition from the previous slide is in `k8s/haproxy.yaml`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the HAProxy pod:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/haproxy.yaml
|
||||
```
|
||||
|
||||
<!-- ```hide kubectl wait pod haproxy --for condition=ready``` -->
|
||||
|
||||
- Check the IP address allocated to the pod:
|
||||
```bash
|
||||
kubectl get pod haproxy -o wide
|
||||
IP=$(kubectl get pod haproxy -o json | jq -r .status.podIP)
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Testing our load balancer
|
||||
|
||||
- The load balancer will send:
|
||||
|
||||
- half of the connections to Google
|
||||
|
||||
- the other half to Bing
|
||||
|
||||
.exercise[
|
||||
|
||||
- Access the load balancer a few times:
|
||||
```bash
|
||||
curl -I $IP
|
||||
curl -I $IP
|
||||
curl -I $IP
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should see connections served by Google (look for the `Location` header) and others served by Bing (indicated by the `X-MSEdge-Ref` header).
|
||||
|
||||
---
|
||||
|
||||
## Exposing configmaps with the downward API
|
||||
|
||||
- We are going to run a Docker registry on a custom port
|
||||
|
||||
- By default, the registry listens on port 5000
|
||||
|
||||
- This can be changed by setting environment variable `REGISTRY_HTTP_ADDR`
|
||||
|
||||
- We are going to store the port number in a configmap
|
||||
|
||||
- Then we will expose that configmap to a container environment variable
|
||||
|
||||
---
|
||||
|
||||
## Creating the configmap
|
||||
|
||||
.exercise[
|
||||
|
||||
- Our configmap will have a single key, `http.addr`:
|
||||
```bash
|
||||
kubectl create configmap registry --from-literal=http.addr=0.0.0.0:80
|
||||
```
|
||||
|
||||
- Check our configmap:
|
||||
```bash
|
||||
kubectl get configmap registry -o yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Using the configmap
|
||||
|
||||
We are going to use the following pod definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: registry
|
||||
spec:
|
||||
containers:
|
||||
- name: registry
|
||||
image: registry
|
||||
env:
|
||||
- name: REGISTRY_HTTP_ADDR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: registry
|
||||
key: http.addr
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using the configmap
|
||||
|
||||
- The resource definition from the previous slide is in `k8s/registry.yaml`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the registry pod:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/registry.yaml
|
||||
```
|
||||
|
||||
<!-- ```hide kubectl wait pod registry --for condition=ready``` -->
|
||||
|
||||
- Check the IP address allocated to the pod:
|
||||
```bash
|
||||
kubectl get pod registry -o wide
|
||||
IP=$(kubectl get pod registry -o json | jq -r .status.podIP)
|
||||
```
|
||||
|
||||
- Confirm that the registry is available on port 80:
|
||||
```bash
|
||||
curl $IP/v2/_catalog
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Passwords, tokens, sensitive information
|
||||
|
||||
- For sensitive information, there is another special resource: *Secrets*
|
||||
|
||||
- Secrets and Configmaps work almost the same way
|
||||
|
||||
(we'll expose the differences on the next slide)
|
||||
|
||||
- The *intent* is different, though:
|
||||
|
||||
*"You should use secrets for things which are actually secret like API keys,
|
||||
credentials, etc., and use config map for not-secret configuration data."*
|
||||
|
||||
*"In the future there will likely be some differentiators for secrets like rotation or support for backing the secret API w/ HSMs, etc."*
|
||||
|
||||
(Source: [the author of both features](https://stackoverflow.com/a/36925553/580281
|
||||
))
|
||||
|
||||
---
|
||||
|
||||
## Differences between configmaps and secrets
|
||||
|
||||
- Secrets are base64-encoded when shown with `kubectl get secrets -o yaml`
|
||||
|
||||
- keep in mind that this is just *encoding*, not *encryption*
|
||||
|
||||
- it is very easy to [automatically extract and decode secrets](https://medium.com/@mveritym/decoding-kubernetes-secrets-60deed7a96a3)
|
||||
|
||||
- [Secrets can be encrypted at rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
|
||||
|
||||
- With RBAC, we can authorize a user to access configmaps, but not secrets
|
||||
|
||||
(since they are two different kinds of resources)
|
||||
@@ -1,239 +0,0 @@
|
||||
# Git-based workflows
|
||||
|
||||
- Deploying with `kubectl` has downsides:
|
||||
|
||||
- we don't know *who* deployed *what* and *when*
|
||||
|
||||
- there is no audit trail (except the API server logs)
|
||||
|
||||
- there is no easy way to undo most operations
|
||||
|
||||
- there is no review/approval process (like for code reviews)
|
||||
|
||||
- We have all these things for *code*, though
|
||||
|
||||
- Can we manage cluster state like we manage our source code?
|
||||
|
||||
---
|
||||
|
||||
## Reminder: Kubernetes is *declarative*
|
||||
|
||||
- All we do is create/change resources
|
||||
|
||||
- These resources have a perfect YAML representation
|
||||
|
||||
- All we do is manipulating these YAML representations
|
||||
|
||||
(`kubectl run` generates a YAML file that gets applied)
|
||||
|
||||
- We can store these YAML representations in a code repository
|
||||
|
||||
- We can version that code repository and maintain it with best practices
|
||||
|
||||
- define which branch(es) can go to qa/staging/production
|
||||
|
||||
- control who can push to which branches
|
||||
|
||||
- have formal review processes, pull requests ...
|
||||
|
||||
---
|
||||
|
||||
## Enabling git-based workflows
|
||||
|
||||
- There are a few tools out there to help us do that
|
||||
|
||||
- We'll see demos of two of them: [Flux] and [Gitkube]
|
||||
|
||||
- There are *many* other tools, some of them with even more features
|
||||
|
||||
- There are also *many* integrations with popular CI/CD systems
|
||||
|
||||
(e.g.: GitLab, Jenkins, ...)
|
||||
|
||||
[Flux]: https://www.weave.works/oss/flux/
|
||||
[Gitkube]: https://gitkube.sh/
|
||||
|
||||
---
|
||||
|
||||
## Flux overview
|
||||
|
||||
- We put our Kubernetes resources as YAML files in a git repository
|
||||
|
||||
- Flux polls that repository regularly (every 5 minutes by default)
|
||||
|
||||
- The resources described by the YAML files are created/updated automatically
|
||||
|
||||
- Changes are made by updating the code in the repository
|
||||
|
||||
---
|
||||
|
||||
## Preparing a repository for Flux
|
||||
|
||||
- We need a repository with Kubernetes YAML files
|
||||
|
||||
- I have one: https://github.com/jpetazzo/kubercoins
|
||||
|
||||
- Fork it to your GitHub account
|
||||
|
||||
- Create a new branch in your fork; e.g. `prod`
|
||||
|
||||
(e.g. by adding a line in the README through the GitHub web UI)
|
||||
|
||||
- This is the branch that we are going to use for deployment
|
||||
|
||||
---
|
||||
|
||||
## Setting up Flux
|
||||
|
||||
- Clone the Flux repository:
|
||||
```
|
||||
git clone https://github.com/weaveworks/flux
|
||||
```
|
||||
|
||||
- Edit `deploy/flux-deployment.yaml`
|
||||
|
||||
- Change the `--git-url` and `--git-branch` parameters:
|
||||
```yaml
|
||||
- --git-url=git@github.com:your-git-username/kubercoins
|
||||
- --git-branch=prod
|
||||
```
|
||||
|
||||
- Apply all the YAML:
|
||||
```
|
||||
kubectl apply -f deploy/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Allowing Flux to access the repository
|
||||
|
||||
- When it starts, Flux generates an SSH key
|
||||
|
||||
- Display that key:
|
||||
```
|
||||
kubectl get logs deployment flux | grep identity
|
||||
```
|
||||
|
||||
- Then add that key to the repository, giving it **write** access
|
||||
|
||||
(some Flux features require write access)
|
||||
|
||||
- After a minute or so, DockerCoins will be deployed to the current namespace
|
||||
|
||||
---
|
||||
|
||||
## Making changes
|
||||
|
||||
- Make changes (on the `prod` branch), e.g. change `replicas` in `worker`
|
||||
|
||||
- After a few minutes, the changes will be picked up by Flux and applied
|
||||
|
||||
---
|
||||
|
||||
## Other features
|
||||
|
||||
- Flux can keep a list of all the tags of all the images we're running
|
||||
|
||||
- The `fluxctl` tool can show us if we're running the latest images
|
||||
|
||||
- We can also "automate" a resource (i.e. automatically deploy new images)
|
||||
|
||||
- And much more!
|
||||
|
||||
---
|
||||
|
||||
## Gitkube overview
|
||||
|
||||
- We put our Kubernetes resources as YAML files in a git repository
|
||||
|
||||
- Gitkube is a git server (or "git remote")
|
||||
|
||||
- After making changes to the repository, we push to Gitkube
|
||||
|
||||
- Gitkube applies the resources to the cluster
|
||||
|
||||
---
|
||||
|
||||
## Setting up Gitkube
|
||||
|
||||
- Install the CLI:
|
||||
```
|
||||
sudo curl -L -o /usr/local/bin/gitkube \
|
||||
https://github.com/hasura/gitkube/releases/download/v0.2.1/gitkube_linux_amd64
|
||||
sudo chmod +x /usr/local/bin/gitkube
|
||||
```
|
||||
|
||||
- Install Gitkube on the cluster:
|
||||
```
|
||||
gitkube install --expose ClusterIP
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating a Remote
|
||||
|
||||
- Gitkube provides a new type of API resource: *Remote*
|
||||
|
||||
(this is using a mechanism called Custom Resource Definitions or CRD)
|
||||
|
||||
- Create and apply a YAML file containing the following manifest:
|
||||
```yaml
|
||||
apiVersion: gitkube.sh/v1alpha1
|
||||
kind: Remote
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
authorizedKeys:
|
||||
- `ssh-rsa AAA...`
|
||||
manifests:
|
||||
path: "."
|
||||
```
|
||||
|
||||
(replace the `ssh-rsa AAA...` section with the content of `~/.ssh/id_rsa.pub`)
|
||||
|
||||
---
|
||||
|
||||
## Pushing to our remote
|
||||
|
||||
- Get the `gitkubed` IP address:
|
||||
```
|
||||
kubectl -n kube-system get svc gitkubed
|
||||
IP=$(kubectl -n kube-system get svc gitkubed -o json |
|
||||
jq -r .spec.clusterIP)
|
||||
```
|
||||
|
||||
- Get ourselves a sample repository with resource YAML files:
|
||||
```
|
||||
git clone git://github.com/jpetazzo/kubercoins
|
||||
cd kubercoins
|
||||
```
|
||||
|
||||
- Add the remote and push to it:
|
||||
```
|
||||
git remote add k8s ssh://default-example@$IP/~/git/default-example
|
||||
git push k8s master
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Making changes
|
||||
|
||||
- Edit a local file
|
||||
|
||||
- Commit
|
||||
|
||||
- Push!
|
||||
|
||||
- Make sure that you push to the `k8s` remote
|
||||
|
||||
---
|
||||
|
||||
## Other features
|
||||
|
||||
- Gitkube can also build container images for us
|
||||
|
||||
(see the [documentation](https://github.com/hasura/gitkube/blob/master/docs/remote.md) for more details)
|
||||
|
||||
- Gitkube can also deploy Helm Charts
|
||||
|
||||
(instead of raw YAML files)
|
||||
@@ -1,178 +0,0 @@
|
||||
# Healthchecks
|
||||
|
||||
- Kubernetes provides two kinds of healthchecks: liveness and readiness
|
||||
|
||||
- Healthchecks are *probes* that apply to *containers* (not to pods)
|
||||
|
||||
- Each container can have two (optional) probes:
|
||||
|
||||
- liveness = is this container dead or alive?
|
||||
|
||||
- readiness = is this container ready to serve traffic?
|
||||
|
||||
- Different probes are available (HTTP, TCP, program execution)
|
||||
|
||||
- Let's see the difference and how to use them!
|
||||
|
||||
---
|
||||
|
||||
## Liveness probe
|
||||
|
||||
- Indicates if the container is dead or alive
|
||||
|
||||
- A dead container cannot come back to life
|
||||
|
||||
- If the liveness probe fails, the container is killed
|
||||
|
||||
(to make really sure that it's really dead; no zombies or undeads!)
|
||||
|
||||
- What happens next depends on the pod's `restartPolicy`:
|
||||
|
||||
- `Never`: the container is not restarted
|
||||
|
||||
- `OnFailure` or `Always`: the container is restarted
|
||||
|
||||
---
|
||||
|
||||
## When to use a liveness probe
|
||||
|
||||
- To indicate failures that can't be recovered
|
||||
|
||||
- deadlocks (causing all requests to time out)
|
||||
|
||||
- internal corruption (causing all requests to error)
|
||||
|
||||
- If the liveness probe fails *N* consecutive times, the container is killed
|
||||
|
||||
- *N* is the `failureThreshold` (3 by default)
|
||||
|
||||
---
|
||||
|
||||
## Readiness probe
|
||||
|
||||
- Indicates if the container is ready to serve traffic
|
||||
|
||||
- If a container becomes "unready" (let's say busy!) it might be ready again soon
|
||||
|
||||
- If the readiness probe fails:
|
||||
|
||||
- the container is *not* killed
|
||||
|
||||
- if the pod is a member of a service, it is temporarily removed
|
||||
|
||||
- it is re-added as soon as the readiness probe passes again
|
||||
|
||||
---
|
||||
|
||||
## When to use a readiness probe
|
||||
|
||||
- To indicate temporary failures
|
||||
|
||||
- the application can only service *N* parallel connections
|
||||
|
||||
- the runtime is busy doing garbage collection or initial data load
|
||||
|
||||
- The container is marked as "not ready" after `failureThreshold` failed attempts
|
||||
|
||||
(3 by default)
|
||||
|
||||
- It is marked again as "ready" after `successThreshold` successful attempts
|
||||
|
||||
(1 by default)
|
||||
|
||||
---
|
||||
|
||||
## Different types of probes
|
||||
|
||||
- HTTP request
|
||||
|
||||
- specify URL of the request (and optional headers)
|
||||
|
||||
- any status code between 200 and 399 indicates success
|
||||
|
||||
- TCP connection
|
||||
|
||||
- the probe succeeds if the TCP port is open
|
||||
|
||||
- arbitrary exec
|
||||
|
||||
- a command is executed in the container
|
||||
|
||||
- exit status of zero indicates success
|
||||
|
||||
---
|
||||
|
||||
## Benefits of using probes
|
||||
|
||||
- Rolling updates proceed when containers are *actually ready*
|
||||
|
||||
(as opposed to merely started)
|
||||
|
||||
- Containers in a broken state gets killed and restarted
|
||||
|
||||
(instead of serving errors or timeouts)
|
||||
|
||||
- Overloaded backends get removed from load balancer rotation
|
||||
|
||||
(thus improving response times across the board)
|
||||
|
||||
---
|
||||
|
||||
## Example: HTTP probe
|
||||
|
||||
Here is a pod template for the `rng` web service of the DockerCoins app:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: rng-with-liveness
|
||||
spec:
|
||||
containers:
|
||||
- name: rng
|
||||
image: dockercoins/rng:v0.1
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 1
|
||||
```
|
||||
|
||||
If the backend serves an error, or takes longer than 1s, 3 times in a row, it gets killed.
|
||||
|
||||
---
|
||||
|
||||
## Example: exec probe
|
||||
|
||||
Here is a pod template for a Redis server:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: redis-with-liveness
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["redis-cli", "ping"]
|
||||
```
|
||||
|
||||
If the Redis process becomes unresponsive, it will be killed.
|
||||
|
||||
---
|
||||
|
||||
## Details about liveness and readiness probes
|
||||
|
||||
- Probes are executed at intervals of `periodSeconds` (default: 10)
|
||||
|
||||
- The timeout for a probe is set with `timeoutSeconds` (default: 1)
|
||||
|
||||
- A probe is considered successful after `successThreshold` successes (default: 1)
|
||||
|
||||
- A probe is considered failing after `failureThreshold` failures (default: 3)
|
||||
|
||||
- If a probe is not defined, it's as if there was an "always successful" probe
|
||||
@@ -1,524 +0,0 @@
|
||||
# Exposing HTTP services with Ingress resources
|
||||
|
||||
- *Services* give us a way to access a pod or a set of pods
|
||||
|
||||
- Services can be exposed to the outside world:
|
||||
|
||||
- with type `NodePort` (on a port >30000)
|
||||
|
||||
- with type `LoadBalancer` (allocating an external load balancer)
|
||||
|
||||
- What about HTTP services?
|
||||
|
||||
- how can we expose `webui`, `rng`, `hasher`?
|
||||
|
||||
- the Kubernetes dashboard?
|
||||
|
||||
- a new version of `webui`?
|
||||
|
||||
---
|
||||
|
||||
## Exposing HTTP services
|
||||
|
||||
- If we use `NodePort` services, clients have to specify port numbers
|
||||
|
||||
(i.e. http://xxxxx:31234 instead of just http://xxxxx)
|
||||
|
||||
- `LoadBalancer` services are nice, but:
|
||||
|
||||
- they are not available in all environments
|
||||
|
||||
- they often carry an additional cost (e.g. they provision an ELB)
|
||||
|
||||
- they require one extra step for DNS integration
|
||||
<br/>
|
||||
(waiting for the `LoadBalancer` to be provisioned; then adding it to DNS)
|
||||
|
||||
- We could build our own reverse proxy
|
||||
|
||||
---
|
||||
|
||||
## Building a custom reverse proxy
|
||||
|
||||
- There are many options available:
|
||||
|
||||
Apache, HAProxy, Hipache, NGINX, Traefik, ...
|
||||
|
||||
(look at [jpetazzo/aiguillage](https://github.com/jpetazzo/aiguillage) for a minimal reverse proxy configuration using NGINX)
|
||||
|
||||
- Most of these options require us to update/edit configuration files after each change
|
||||
|
||||
- Some of them can pick up virtual hosts and backends from a configuration store
|
||||
|
||||
- Wouldn't it be nice if this configuration could be managed with the Kubernetes API?
|
||||
|
||||
--
|
||||
|
||||
- Enter.red[¹] *Ingress* resources!
|
||||
|
||||
.footnote[.red[¹] Pun maybe intended.]
|
||||
|
||||
---
|
||||
|
||||
## Ingress resources
|
||||
|
||||
- Kubernetes API resource (`kubectl get ingress`/`ingresses`/`ing`)
|
||||
|
||||
- Designed to expose HTTP services
|
||||
|
||||
- Basic features:
|
||||
|
||||
- load balancing
|
||||
- SSL termination
|
||||
- name-based virtual hosting
|
||||
|
||||
- Can also route to different services depending on:
|
||||
|
||||
- URI path (e.g. `/api`→`api-service`, `/static`→`assets-service`)
|
||||
- Client headers, including cookies (for A/B testing, canary deployment...)
|
||||
- and more!
|
||||
|
||||
---
|
||||
|
||||
## Principle of operation
|
||||
|
||||
- Step 1: deploy an *ingress controller*
|
||||
|
||||
- ingress controller = load balancer + control loop
|
||||
|
||||
- the control loop watches over ingress resources, and configures the LB accordingly
|
||||
|
||||
- Step 2: setup DNS
|
||||
|
||||
- associate DNS entries with the load balancer address
|
||||
|
||||
- Step 3: create *ingress resources*
|
||||
|
||||
- the ingress controller picks up these resources and configures the LB
|
||||
|
||||
- Step 4: profit!
|
||||
|
||||
---
|
||||
|
||||
## Ingress in action
|
||||
|
||||
- We will deploy the Traefik ingress controller
|
||||
|
||||
- this is an arbitrary choice
|
||||
|
||||
- maybe motivated by the fact that Traefik releases are named after cheeses
|
||||
|
||||
- For DNS, we will use [nip.io](http://nip.io/)
|
||||
|
||||
- `*.1.2.3.4.nip.io` resolves to `1.2.3.4`
|
||||
|
||||
- We will create ingress resources for various HTTP services
|
||||
|
||||
---
|
||||
|
||||
## Deploying pods listening on port 80
|
||||
|
||||
- We want our ingress load balancer to be available on port 80
|
||||
|
||||
- We could do that with a `LoadBalancer` service
|
||||
|
||||
... but it requires support from the underlying infrastructure
|
||||
|
||||
- We could use pods specifying `hostPort: 80`
|
||||
|
||||
... but with most CNI plugins, this [doesn't work or require 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
|
||||
|
||||
---
|
||||
|
||||
## Without `hostNetwork`
|
||||
|
||||
- Normally, each pod gets its own *network namespace*
|
||||
|
||||
(sometimes called sandbox or network sandbox)
|
||||
|
||||
- An IP address is associated to the pod
|
||||
|
||||
- This IP address is routed/connected to the cluster network
|
||||
|
||||
- All containers of that pod are sharing that network namespace
|
||||
|
||||
(and therefore using the same IP address)
|
||||
|
||||
---
|
||||
|
||||
## With `hostNetwork: true`
|
||||
|
||||
- No network namespace gets created
|
||||
|
||||
- The pod is using the network namespace of the host
|
||||
|
||||
- It "sees" (and can use) the interfaces (and IP addresses) of the host
|
||||
|
||||
- The pod can receive outside traffic directly, on any port
|
||||
|
||||
- Downside: with most network plugins, network policies won't work for that pod
|
||||
|
||||
- most network policies work at the IP address level
|
||||
|
||||
- filtering that pod = filtering traffic from the node
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
- We are going to use a Daemon Set so that each node can accept connections
|
||||
|
||||
- We will do two minor changes to the [YAML provided by Traefik](https://github.com/containous/traefik/blob/master/examples/k8s/traefik-ds.yaml):
|
||||
|
||||
- enable `hostNetwork`
|
||||
|
||||
- add a *toleration* so that Traefik also runs on `node1`
|
||||
|
||||
---
|
||||
|
||||
## Taints and tolerations
|
||||
|
||||
- A *taint* is an attribute added to a node
|
||||
|
||||
- It prevents pods from running on the node
|
||||
|
||||
- ... Unless they have a matching *toleration*
|
||||
|
||||
- When deploying with `kubeadm`:
|
||||
|
||||
- a taint is placed on the node dedicated the control plane
|
||||
|
||||
- the pods running the control plane have a matching toleration
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Checking taints on our nodes
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check our nodes specs:
|
||||
```bash
|
||||
kubectl get node node1 -o json | jq .spec
|
||||
kubectl get node node2 -o json | jq .spec
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should see a result only for `node1` (the one with the control plane):
|
||||
|
||||
```json
|
||||
"taints": [
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node-role.kubernetes.io/master"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Understanding a taint
|
||||
|
||||
- The `key` can be interpreted as:
|
||||
|
||||
- a reservation for a special set of pods
|
||||
<br/>
|
||||
(here, this means "this node is reserved for the control plane")
|
||||
|
||||
- an error condition on the node
|
||||
<br/>
|
||||
(for instance: "disk full", do not start new pods here!)
|
||||
|
||||
- The `effect` can be:
|
||||
|
||||
- `NoSchedule` (don't run new pods here)
|
||||
|
||||
- `PreferNoSchedule` (try not to run new pods here)
|
||||
|
||||
- `NoExecute` (don't run new pods and evict running pods)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Checking tolerations on the control plane
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check tolerations for CoreDNS:
|
||||
```bash
|
||||
kubectl -n kube-system get deployments coredns -o json |
|
||||
jq .spec.template.spec.tolerations
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The result should include:
|
||||
```json
|
||||
{
|
||||
"effect": "NoSchedule",
|
||||
"key": "node-role.kubernetes.io/master"
|
||||
}
|
||||
```
|
||||
|
||||
It means: "bypass the exact taint that we saw earlier on `node1`."
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Special tolerations
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check tolerations on `kube-proxy`:
|
||||
```bash
|
||||
kubectl -n kube-system get ds kube-proxy -o json |
|
||||
jq .spec.template.spec.tolerations
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The result should include:
|
||||
```json
|
||||
{
|
||||
"operator": "Exists"
|
||||
}
|
||||
```
|
||||
|
||||
This one is a special case that means "ignore all taints and run anyway."
|
||||
|
||||
---
|
||||
|
||||
## Running Traefik on our cluster
|
||||
|
||||
- We provide a YAML file (`k8s/traefik.yaml`) which is essentially the sum of:
|
||||
|
||||
- [Traefik's Daemon Set resources](https://github.com/containous/traefik/blob/master/examples/k8s/traefik-ds.yaml) (patched with `hostNetwork` and tolerations)
|
||||
|
||||
- [Traefik's RBAC rules](https://github.com/containous/traefik/blob/master/examples/k8s/traefik-rbac.yaml) allowing it to watch necessary API objects
|
||||
|
||||
.exercise[
|
||||
|
||||
- Apply the YAML:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/traefik.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Checking that Traefik runs correctly
|
||||
|
||||
- If Traefik started correctly, we now have a web server listening on each node
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check that Traefik is serving 80/tcp:
|
||||
```bash
|
||||
curl localhost
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We should get a `404 page not found` error.
|
||||
|
||||
This is normal: we haven't provided any ingress rule yet.
|
||||
|
||||
---
|
||||
|
||||
## Setting up DNS
|
||||
|
||||
- To make our lives easier, we will use [nip.io](http://nip.io)
|
||||
|
||||
- Check out `http://cheddar.A.B.C.D.nip.io`
|
||||
|
||||
(replacing A.B.C.D with the IP address of `node1`)
|
||||
|
||||
- We should get the same `404 page not found` error
|
||||
|
||||
(meaning that our DNS is "set up properly", so to speak!)
|
||||
|
||||
---
|
||||
|
||||
## Traefik web UI
|
||||
|
||||
- Traefik provides a web dashboard
|
||||
|
||||
- With the current install method, it's listening on port 8080
|
||||
|
||||
.exercise[
|
||||
|
||||
- Go to `http://node1:8080` (replacing `node1` with its IP address)
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Setting up host-based routing ingress rules
|
||||
|
||||
- We are going to use `errm/cheese` images
|
||||
|
||||
(there are [3 tags available](https://hub.docker.com/r/errm/cheese/tags/): wensleydale, cheddar, stilton)
|
||||
|
||||
- These images contain a simple static HTTP server sending a picture of cheese
|
||||
|
||||
- We will run 3 deployments (one for each cheese)
|
||||
|
||||
- We will create 3 services (one for each deployment)
|
||||
|
||||
- Then we will create 3 ingress rules (one for each service)
|
||||
|
||||
- We will route `<name-of-cheese>.A.B.C.D.nip.io` to the corresponding deployment
|
||||
|
||||
---
|
||||
|
||||
## Running cheesy web servers
|
||||
|
||||
.exercise[
|
||||
|
||||
- Run all three deployments:
|
||||
```bash
|
||||
kubectl run cheddar --image=errm/cheese:cheddar
|
||||
kubectl run stilton --image=errm/cheese:stilton
|
||||
kubectl run wensleydale --image=errm/cheese:wensleydale
|
||||
```
|
||||
|
||||
- Create a service for each of them:
|
||||
```bash
|
||||
kubectl expose deployment cheddar --port=80
|
||||
kubectl expose deployment stilton --port=80
|
||||
kubectl expose deployment wensleydale --port=80
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## What does an ingress resource look like?
|
||||
|
||||
Here is a minimal host-based ingress resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheddar
|
||||
spec:
|
||||
rules:
|
||||
- host: cheddar.`A.B.C.D`.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: 80
|
||||
|
||||
```
|
||||
|
||||
(It is in `k8s/ingress.yaml`.)
|
||||
|
||||
---
|
||||
|
||||
## Creating our first ingress resources
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit the file `~/container.training/k8s/ingress.yaml`
|
||||
|
||||
- Replace A.B.C.D with the IP address of `node1`
|
||||
|
||||
- Apply the file
|
||||
|
||||
- Open http://cheddar.A.B.C.D.nip.io
|
||||
|
||||
]
|
||||
|
||||
(An image of a piece of cheese should show up.)
|
||||
|
||||
---
|
||||
|
||||
## Creating the other ingress resources
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit the file `~/container.training/k8s/ingress.yaml`
|
||||
|
||||
- Replace `cheddar` with `stilton` (in `name`, `host`, `serviceName`)
|
||||
|
||||
- Apply the file
|
||||
|
||||
- Check that `stilton.A.B.C.D.nip.io` works correctly
|
||||
|
||||
- Repeat for `wensleydale`
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Using multiple ingress controllers
|
||||
|
||||
- You can have multiple ingress controllers active simultaneously
|
||||
|
||||
(e.g. Traefik and NGINX)
|
||||
|
||||
- You can even have multiple instances of the same controller
|
||||
|
||||
(e.g. one for internal, another for external traffic)
|
||||
|
||||
- The `kubernetes.io/ingress.class` annotation can be used to tell which one to use
|
||||
|
||||
- It's OK if multiple ingress controllers configure the same resource
|
||||
|
||||
(it just means that the service will be accessible through multiple paths)
|
||||
|
||||
---
|
||||
|
||||
## Ingress: the good
|
||||
|
||||
- The traffic flows directly from the ingress load balancer to the backends
|
||||
|
||||
- it doesn't need to go through the `ClusterIP`
|
||||
|
||||
- in fact, we don't even need a `ClusterIP` (we can use a headless service)
|
||||
|
||||
- The load balancer can be outside of Kubernetes
|
||||
|
||||
(as long as it has access to the cluster subnet)
|
||||
|
||||
- This allows to use external (hardware, physical machines...) load balancers
|
||||
|
||||
- Annotations can encode special features
|
||||
|
||||
(rate-limiting, A/B testing, session stickiness, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Ingress: the bad
|
||||
|
||||
- Aforementioned "special features" are not standardized yet
|
||||
|
||||
- Some controllers will support them; some won't
|
||||
|
||||
- Even relatively common features (stripping a path prefix) can differ:
|
||||
|
||||
- [traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip](https://docs.traefik.io/user-guide/kubernetes/#path-based-routing)
|
||||
|
||||
- [ingress.kubernetes.io/rewrite-target: /](https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx/examples/rewrite)
|
||||
|
||||
- This should eventually stabilize
|
||||
|
||||
(remember that ingresses are currently `apiVersion: extensions/v1beta1`)
|
||||
@@ -1,184 +0,0 @@
|
||||
# Accessing the API with `kubectl proxy`
|
||||
|
||||
- The API requires us to authenticate.red[¹]
|
||||
|
||||
- There are many authentication methods available, including:
|
||||
|
||||
- TLS client certificates
|
||||
<br/>
|
||||
(that's what we've used so far)
|
||||
|
||||
- HTTP basic password authentication
|
||||
<br/>
|
||||
(from a static file; not recommended)
|
||||
|
||||
- various token mechanisms
|
||||
<br/>
|
||||
(detailed in the [documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authentication-strategies))
|
||||
|
||||
.red[¹]OK, we lied. If you don't authenticate, you are considered to
|
||||
be user `system:anonymous`, which doesn't have any access rights by default.
|
||||
|
||||
---
|
||||
|
||||
## Accessing the API directly
|
||||
|
||||
- Let's see what happens if we try to access the API directly with `curl`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Retrieve the ClusterIP allocated to the `kubernetes` service:
|
||||
```bash
|
||||
kubectl get svc kubernetes
|
||||
```
|
||||
|
||||
- Replace the IP below and try to connect with `curl`:
|
||||
```bash
|
||||
curl -k https://`10.96.0.1`/
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The API will tell us that user `system:anonymous` cannot access this path.
|
||||
|
||||
---
|
||||
|
||||
## Authenticating to the API
|
||||
|
||||
If we wanted to talk to the API, we would need to:
|
||||
|
||||
- extract our TLS key and certificate information from `~/.kube/config`
|
||||
|
||||
(the information is in PEM format, encoded in base64)
|
||||
|
||||
- use that information to present our certificate when connecting
|
||||
|
||||
(for instance, with `openssl s_client -key ... -cert ... -connect ...`)
|
||||
|
||||
- figure out exactly which credentials to use
|
||||
|
||||
(once we start juggling multiple clusters)
|
||||
|
||||
- change that whole process if we're using another authentication method
|
||||
|
||||
🤔 There has to be a better way!
|
||||
|
||||
---
|
||||
|
||||
## Using `kubectl proxy` for authentication
|
||||
|
||||
- `kubectl proxy` runs a proxy in the foreground
|
||||
|
||||
- This proxy lets us access the Kubernetes API without authentication
|
||||
|
||||
(`kubectl proxy` adds our credentials on the fly to the requests)
|
||||
|
||||
- This proxy lets us access the Kubernetes API over plain HTTP
|
||||
|
||||
- This is a great tool to learn and experiment with the Kubernetes API
|
||||
|
||||
- ... And for serious usages as well (suitable for one-shot scripts)
|
||||
|
||||
- For unattended use, it is better to create a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
|
||||
|
||||
---
|
||||
|
||||
## Trying `kubectl proxy`
|
||||
|
||||
- Let's start `kubectl proxy` and then do a simple request with `curl`!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Start `kubectl proxy` in the background:
|
||||
```bash
|
||||
kubectl proxy &
|
||||
```
|
||||
|
||||
- Access the API's default route:
|
||||
```bash
|
||||
curl localhost:8001
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait /version```
|
||||
```keys ^J```
|
||||
-->
|
||||
|
||||
- Terminate the proxy:
|
||||
```bash
|
||||
kill %1
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The output is a list of available API routes.
|
||||
|
||||
---
|
||||
|
||||
## `kubectl proxy` is intended for local use
|
||||
|
||||
- By default, the proxy listens on port 8001
|
||||
|
||||
(But this can be changed, or we can tell `kubectl proxy` to pick a port)
|
||||
|
||||
- By default, the proxy binds to `127.0.0.1`
|
||||
|
||||
(Making it unreachable from other machines, for security reasons)
|
||||
|
||||
- By default, the proxy only accepts connections from:
|
||||
|
||||
`^localhost$,^127\.0\.0\.1$,^\[::1\]$`
|
||||
|
||||
- This is great when running `kubectl proxy` locally
|
||||
|
||||
- Not-so-great when you want to connect to the proxy from a remote machine
|
||||
|
||||
---
|
||||
|
||||
## Running `kubectl proxy` on a remote machine
|
||||
|
||||
- If we wanted to connect to the proxy from another machine, we would need to:
|
||||
|
||||
- bind to `INADDR_ANY` instead of `127.0.0.1`
|
||||
|
||||
- accept connections from any address
|
||||
|
||||
- This is achieved with:
|
||||
```
|
||||
kubectl proxy --port=8888 --address=0.0.0.0 --accept-hosts=.*
|
||||
```
|
||||
|
||||
.warning[Do not do this on a real cluster: it opens full unauthenticated access!]
|
||||
|
||||
---
|
||||
|
||||
## Security considerations
|
||||
|
||||
- Running `kubectl proxy` openly is a huge security risk
|
||||
|
||||
- It is slightly better to run the proxy where you need it
|
||||
|
||||
(and copy credentials, e.g. `~/.kube/config`, to that place)
|
||||
|
||||
- It is even better to use a limited account with reduced permissions
|
||||
|
||||
---
|
||||
|
||||
## Good to know ...
|
||||
|
||||
- `kubectl proxy` also gives access to all internal services
|
||||
|
||||
- Specifically, services are exposed as such:
|
||||
```
|
||||
/api/v1/namespaces/<namespace>/services/<service>/proxy
|
||||
```
|
||||
|
||||
- We can use `kubectl proxy` to access an internal service in a pinch
|
||||
|
||||
(or, for non HTTP services, `kubectl port-forward`)
|
||||
|
||||
- This is not very useful when running `kubectl` directly on the cluster
|
||||
|
||||
(since we could connect to the services directly anyway)
|
||||
|
||||
- But it is very powerful as soon as you run `kubectl` from a remote machine
|
||||
@@ -1,20 +0,0 @@
|
||||
# Links and resources
|
||||
|
||||
All things Kubernetes:
|
||||
|
||||
- [Kubernetes Community](https://kubernetes.io/community/) - Slack, Google Groups, meetups
|
||||
- [Kubernetes on StackOverflow](https://stackoverflow.com/questions/tagged/kubernetes)
|
||||
- [Play With Kubernetes Hands-On Labs](https://medium.com/@marcosnils/introducing-pwk-play-with-k8s-159fcfeb787b)
|
||||
|
||||
All things Docker:
|
||||
|
||||
- [Docker documentation](http://docs.docker.com/)
|
||||
- [Docker Hub](https://hub.docker.com)
|
||||
- [Docker on StackOverflow](https://stackoverflow.com/questions/tagged/docker)
|
||||
- [Play With Docker Hands-On Labs](http://training.play-with-docker.com/)
|
||||
|
||||
Everything else:
|
||||
|
||||
- [Local meetups](https://www.meetup.com/)
|
||||
|
||||
.footnote[These slides (and future updates) are on → http://container.training/]
|
||||
@@ -1,156 +0,0 @@
|
||||
# Controlling the cluster remotely
|
||||
|
||||
- All the operations that we do with `kubectl` can be done remotely
|
||||
|
||||
- In this section, we are going to use `kubectl` from our local machine
|
||||
|
||||
---
|
||||
|
||||
## Installing `kubectl`
|
||||
|
||||
- If you already have `kubectl` on your local machine, you can skip this
|
||||
|
||||
.exercise[
|
||||
|
||||
- Download the `kubectl` binary from one of these links:
|
||||
|
||||
[Linux](https://storage.googleapis.com/kubernetes-release/release/v1.11.2/bin/linux/amd64/kubectl)
|
||||
|
|
||||
[macOS](https://storage.googleapis.com/kubernetes-release/release/v1.11.2/bin/darwin/amd64/kubectl)
|
||||
|
|
||||
[Windows](https://storage.googleapis.com/kubernetes-release/release/v1.11.2/bin/windows/amd64/kubectl.exe)
|
||||
|
||||
- On Linux and macOS, make the binary executable with `chmod +x kubectl`
|
||||
|
||||
(And remember to run it with `./kubectl` or move it to your `$PATH`)
|
||||
|
||||
]
|
||||
|
||||
Note: if you are following along with a different platform (e.g. Linux on an architecture different from amd64, or with a phone or tablet), installing `kubectl` might be more complicated (or even impossible) so feel free to skip this section.
|
||||
|
||||
---
|
||||
|
||||
## Testing `kubectl`
|
||||
|
||||
- Check that `kubectl` works correctly
|
||||
|
||||
(before even trying to connect to a remote cluster!)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Ask `kubectl` to show its version number:
|
||||
```bash
|
||||
kubectl version --client
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The output should look like this:
|
||||
```
|
||||
Client Version: version.Info{Major:"1", Minor:"11", GitVersion:"v1.11.2",
|
||||
GitCommit:"bb9ffb1654d4a729bb4cec18ff088eacc153c239", GitTreeState:"clean",
|
||||
BuildDate:"2018-08-07T23:17:28Z", GoVersion:"go1.10.3", Compiler:"gc",
|
||||
Platform:"linux/amd64"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Moving away the existing `~/.kube/config`
|
||||
|
||||
- If you already have a `~/.kube/config` file, move it away
|
||||
|
||||
(we are going to overwrite it in the following slides!)
|
||||
|
||||
- If you never used `kubectl` on your machine before: nothing to do!
|
||||
|
||||
- If you already used `kubectl` to control a Kubernetes cluster before:
|
||||
|
||||
- rename `~/.kube/config` to e.g. `~/.kube/config.bak`
|
||||
|
||||
---
|
||||
|
||||
## Copying the configuration file from `node1`
|
||||
|
||||
- The `~/.kube/config` file that is on `node1` contains all the credentials we need
|
||||
|
||||
- Let's copy it over!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Copy the file from `node1`; if you are using macOS or Linux, you can do:
|
||||
```
|
||||
scp `USER`@`X.X.X.X`:.kube/config ~/.kube/config
|
||||
# Make sure to replace X.X.X.X with the IP address of node1,
|
||||
# and USER with the user name used to log into node1!
|
||||
```
|
||||
|
||||
- If you are using Windows, adapt these instructions to your SSH client
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Updating the server address
|
||||
|
||||
- There is a good chance that we need to update the server address
|
||||
|
||||
- To know if it is necessary, run `kubectl config view`
|
||||
|
||||
- Look for the `server:` address:
|
||||
|
||||
- if it matches the public IP address of `node1`, you're good!
|
||||
|
||||
- if it is anything else (especially a private IP address), update it!
|
||||
|
||||
- To update the server address, run:
|
||||
```bash
|
||||
kubectl config set-cluster kubernetes --server=https://`X.X.X.X`:6443
|
||||
kubectl config set-cluster kubernetes --insecure-skip-tls-verify
|
||||
# Make sure to replace X.X.X.X with the IP address of node1!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Why do we skip TLS verification?
|
||||
|
||||
- Generally, the Kubernetes API uses a certificate that is valid for:
|
||||
|
||||
- `kubernetes`
|
||||
- `kubernetes.default`
|
||||
- `kubernetes.default.svc`
|
||||
- `kubernetes.default.svc.cluster.local`
|
||||
- the ClusterIP address of the `kubernetes` service
|
||||
- the hostname of the node hosting the control plane (e.g. `node1`)
|
||||
- the IP address of the node hosting the control plane
|
||||
|
||||
- On most clouds, the IP address of the node is an internal IP address
|
||||
|
||||
- ... And we are going to connect over the external IP address
|
||||
|
||||
- ... And that external IP address was not used when creating the certificate!
|
||||
|
||||
.warning[It's better to NOT skip TLS verification; this is for educational purposes only!]
|
||||
|
||||
---
|
||||
|
||||
## Checking that we can connect to the cluster
|
||||
|
||||
- We can now run a couple of trivial commands to check that all is well
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the versions of the local client and remote server:
|
||||
```bash
|
||||
kubectl version
|
||||
```
|
||||
|
||||
- View the nodes of the cluster:
|
||||
```bash
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We can now utilize the cluster exactly as we did before, ignoring that it's remote.
|
||||
@@ -1,420 +0,0 @@
|
||||
# Network policies
|
||||
|
||||
- Namespaces help us to *organize* resources
|
||||
|
||||
- Namespaces do not provide isolation
|
||||
|
||||
- By default, every pod can contact every other pod
|
||||
|
||||
- By default, every service accepts traffic from anyone
|
||||
|
||||
- If we want this to be different, we need *network policies*
|
||||
|
||||
---
|
||||
|
||||
## What's a network policy?
|
||||
|
||||
A network policy is defined by the following things.
|
||||
|
||||
- A *pod selector* indicating which pods it applies to
|
||||
|
||||
e.g.: "all pods in namespace `blue` with the label `zone=internal`"
|
||||
|
||||
- A list of *ingress rules* indicating which inbound traffic is allowed
|
||||
|
||||
e.g.: "TCP connections to ports 8000 and 8080 coming from pods with label `zone=dmz`,
|
||||
and from the external subnet 4.42.6.0/24, except 4.42.6.5"
|
||||
|
||||
- A list of *egress rules* indicating which outbound traffic is allowed
|
||||
|
||||
A network policy can provide ingress rules, egress rules, or both.
|
||||
|
||||
---
|
||||
|
||||
## How do network policies apply?
|
||||
|
||||
- A pod can be "selected" by any number of network policies
|
||||
|
||||
- If a pod isn't selected by any network policy, then its traffic is unrestricted
|
||||
|
||||
(In other words: in the absence of network policies, all traffic is allowed)
|
||||
|
||||
- If a pod is selected by at least one network policy, then all traffic is blocked ...
|
||||
|
||||
... unless it is explicitly allowed by one of these network policies
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Traffic filtering is flow-oriented
|
||||
|
||||
- Network policies deal with *connections*, not individual packets
|
||||
|
||||
- Example: to allow HTTP (80/tcp) connections to pod A, you only need an ingress rule
|
||||
|
||||
(You do not need a matching egress rule to allow response traffic to go through)
|
||||
|
||||
- This also applies for UDP traffic
|
||||
|
||||
(Allowing DNS traffic can be done with a single rule)
|
||||
|
||||
- Network policy implementations use stateful connection tracking
|
||||
|
||||
---
|
||||
|
||||
## Pod-to-pod traffic
|
||||
|
||||
- Connections from pod A to pod B have to be allowed by both pods:
|
||||
|
||||
- pod A has to be unrestricted, or allow the connection as an *egress* rule
|
||||
|
||||
- pod B has to be unrestricted, or allow the connection as an *ingress* rule
|
||||
|
||||
- As a consequence: if a network policy restricts traffic going from/to a pod,
|
||||
<br/>
|
||||
the restriction cannot be overridden by a network policy selecting another pod
|
||||
|
||||
- This prevents an entity managing network policies in namespace A
|
||||
(but without permission to do so in namespace B)
|
||||
from adding network policies giving them access to namespace B
|
||||
|
||||
---
|
||||
|
||||
## The rationale for network policies
|
||||
|
||||
- In network security, it is generally considered better to "deny all, then allow selectively"
|
||||
|
||||
(The other approach, "allow all, then block selectively" makes it too easy to leave holes)
|
||||
|
||||
- As soon as one network policy selects a pod, the pod enters this "deny all" logic
|
||||
|
||||
- Further network policies can open additional access
|
||||
|
||||
- Good network policies should be scoped as precisely as possible
|
||||
|
||||
- In particular: make sure that the selector is not too broad
|
||||
|
||||
(Otherwise, you end up affecting pods that were otherwise well secured)
|
||||
|
||||
---
|
||||
|
||||
## Our first network policy
|
||||
|
||||
This is our game plan:
|
||||
|
||||
- run a web server in a pod
|
||||
|
||||
- create a network policy to block all access to the web server
|
||||
|
||||
- create another network policy to allow access only from specific pods
|
||||
|
||||
---
|
||||
|
||||
## Running our test web server
|
||||
|
||||
.exercise[
|
||||
|
||||
- Let's use the `nginx` image:
|
||||
```bash
|
||||
kubectl run testweb --image=nginx
|
||||
```
|
||||
|
||||
- Find out the IP address of the pod with one of these two commands:
|
||||
```bash
|
||||
kubectl get pods -o wide -l run=testweb
|
||||
IP=$(kubectl get pods -l run=testweb -o json | jq -r .items[0].status.podIP)
|
||||
```
|
||||
|
||||
- Check that we can connect to the server:
|
||||
```bash
|
||||
curl $IP
|
||||
```
|
||||
]
|
||||
|
||||
The `curl` command should show us the "Welcome to nginx!" page.
|
||||
|
||||
---
|
||||
|
||||
## Adding a very restrictive network policy
|
||||
|
||||
- The policy will select pods with the label `run=testweb`
|
||||
|
||||
- It will specify an empty list of ingress rules (matching nothing)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Apply the policy in this YAML file:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/netpol-deny-all-for-testweb.yaml
|
||||
```
|
||||
|
||||
- Check if we can still access the server:
|
||||
```bash
|
||||
curl $IP
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The `curl` command should now time out.
|
||||
|
||||
---
|
||||
|
||||
## Looking at the network policy
|
||||
|
||||
This is the file that we applied:
|
||||
|
||||
```yaml
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: deny-all-for-testweb
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
run: testweb
|
||||
ingress: []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Allowing connections only from specific pods
|
||||
|
||||
- We want to allow traffic from pods with the label `run=testcurl`
|
||||
|
||||
- Reminder: this label is automatically applied when we do `kubectl run testcurl ...`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Apply another policy:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/netpol-allow-testcurl-for-testweb.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Looking at the network policy
|
||||
|
||||
This is the second file that we applied:
|
||||
|
||||
```yaml
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: allow-testcurl-for-testweb
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
run: testweb
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
run: testcurl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the network policy
|
||||
|
||||
- Let's create pods with, and without, the required label
|
||||
|
||||
.exercise[
|
||||
|
||||
- Try to connect to testweb from a pod with the `run=testcurl` label:
|
||||
```bash
|
||||
kubectl run testcurl --rm -i --image=centos -- curl -m3 $IP
|
||||
```
|
||||
|
||||
- Try to connect to testweb with a different label:
|
||||
```bash
|
||||
kubectl run testkurl --rm -i --image=centos -- curl -m3 $IP
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The first command will work (and show the "Welcome to nginx!" page).
|
||||
|
||||
The second command will fail and time out after 3 seconds.
|
||||
|
||||
(The timeout is obtained with the `-m3` option.)
|
||||
|
||||
---
|
||||
|
||||
## An important warning
|
||||
|
||||
- Some network plugins only have partial support for network policies
|
||||
|
||||
- For instance, Weave [doesn't support ipBlock (yet)](https://github.com/weaveworks/weave/issues/3168)
|
||||
|
||||
- Weave added support for egress rules [in version 2.4](https://github.com/weaveworks/weave/pull/3313) (released in July 2018)
|
||||
|
||||
- Unsupported features might be silently ignored
|
||||
|
||||
(Making you believe that you are secure, when you're not)
|
||||
|
||||
---
|
||||
|
||||
## Network policies, pods, and services
|
||||
|
||||
- Network policies apply to *pods*
|
||||
|
||||
- A *service* can select multiple pods
|
||||
|
||||
(And load balance traffic across them)
|
||||
|
||||
- It is possible that we can connect to some pods, but not some others
|
||||
|
||||
(Because of how network policies have been defined for these pods)
|
||||
|
||||
- In that case, connections to the service will randomly pass or fail
|
||||
|
||||
(Depending on whether the connection was sent to a pod that we have access to or not)
|
||||
|
||||
---
|
||||
|
||||
## Network policies and namespaces
|
||||
|
||||
- A good strategy is to isolate a namespace, so that:
|
||||
|
||||
- all the pods in the namespace can communicate together
|
||||
|
||||
- other namespaces cannot access the pods
|
||||
|
||||
- external access has to be enabled explicitly
|
||||
|
||||
- Let's see what this would look like for the DockerCoins app!
|
||||
|
||||
---
|
||||
|
||||
## Network policies for DockerCoins
|
||||
|
||||
- We are going to apply two policies
|
||||
|
||||
- The first policy will prevent traffic from other namespaces
|
||||
|
||||
- The second policy will allow traffic to the `webui` pods
|
||||
|
||||
- That's all we need for that app!
|
||||
|
||||
---
|
||||
|
||||
## Blocking traffic from other namespaces
|
||||
|
||||
This policy selects all pods in the current namespace.
|
||||
|
||||
It allows traffic only from pods in the current namespace.
|
||||
|
||||
(An empty `podSelector` means "all pods".)
|
||||
|
||||
```yaml
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: deny-from-other-namespaces
|
||||
spec:
|
||||
podSelector: {}
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector: {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Allowing traffic to `webui` pods
|
||||
|
||||
This policy selects all pods with label `run=webui`.
|
||||
|
||||
It allows traffic from any source.
|
||||
|
||||
(An empty `from` fields means "all sources".)
|
||||
|
||||
```yaml
|
||||
kind: NetworkPolicy
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: allow-webui
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
run: webui
|
||||
ingress:
|
||||
- from: []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Applying both network policies
|
||||
|
||||
- Both network policies are declared in the file `k8s/netpol-dockercoins.yaml`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Apply the network policies:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/netpol-dockercoins.yaml
|
||||
```
|
||||
|
||||
- Check that we can still access the web UI from outside
|
||||
<br/>
|
||||
(and that the app is still working correctly!)
|
||||
|
||||
- Check that we can't connect anymore to `rng` or `hasher` through their ClusterIP
|
||||
|
||||
]
|
||||
|
||||
Note: using `kubectl proxy` or `kubectl port-forward` allows us to connect
|
||||
regardless of existing network policies. This allows us to debug and
|
||||
troubleshoot easily, without having to poke holes in our firewall.
|
||||
|
||||
---
|
||||
|
||||
## Protecting the control plane
|
||||
|
||||
- Should we add network policies to block unauthorized access to the control plane?
|
||||
|
||||
(etcd, API server, etc.)
|
||||
|
||||
--
|
||||
|
||||
- At first, it seems like a good idea ...
|
||||
|
||||
--
|
||||
|
||||
- But it *shouldn't* be necessary:
|
||||
|
||||
- not all network plugins support network policies
|
||||
|
||||
- the control plane is secured by other methods (mutual TLS, mostly)
|
||||
|
||||
- the code running in our pods can reasonably expect to contact the API
|
||||
<br/>
|
||||
(and it can do so safely thanks to the API permission model)
|
||||
|
||||
- If we block access to the control plane, we might disrupt legitimate code
|
||||
|
||||
- ... Without necessarily improving security
|
||||
|
||||
---
|
||||
|
||||
## Further resources
|
||||
|
||||
- As always, the [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/network-policies/) is a good starting point
|
||||
|
||||
- The API documentation has a lot of detail about the format of various objects:
|
||||
|
||||
- [NetworkPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#networkpolicy-v1-networking-k8s-io)
|
||||
|
||||
- [NetworkPolicySpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#networkpolicyspec-v1-networking-k8s-io)
|
||||
|
||||
- [NetworkPolicyIngressRule](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#networkpolicyingressrule-v1-networking-k8s-io)
|
||||
|
||||
- etc.
|
||||
|
||||
- And two resources by [Ahmet Alp Balkan](https://ahmet.im/):
|
||||
|
||||
- a [very good talk about network policies](https://www.youtube.com/watch?list=PLj6h78yzYM2P-3-xqvmWaZbbI1sW-ulZb&v=3gGpMmYeEO8) at KubeCon North America 2017
|
||||
|
||||
- a repository of [ready-to-use recipes](https://github.com/ahmetb/kubernetes-network-policy-recipes) for network policies
|
||||
@@ -1,177 +0,0 @@
|
||||
# Owners and dependents
|
||||
|
||||
- Some objects are created by other objects
|
||||
|
||||
(example: pods created by replica sets, themselves created by deployments)
|
||||
|
||||
- When an *owner* object is deleted, its *dependents* are deleted
|
||||
|
||||
(this is the default behavior; it can be changed)
|
||||
|
||||
- We can delete a dependent directly if we want
|
||||
|
||||
(but generally, the owner will recreate another right away)
|
||||
|
||||
- An object can have multiple owners
|
||||
|
||||
---
|
||||
|
||||
## Finding out the owners of an object
|
||||
|
||||
- The owners are recorded in the field `ownerReferences` in the `metadata` block
|
||||
|
||||
.exercise[
|
||||
|
||||
- Let's start a replicated `nginx` deployment:
|
||||
```bash
|
||||
kubectl run yanginx --image=nginx --replicas=3
|
||||
```
|
||||
|
||||
- Once it's up, check the corresponding pods:
|
||||
```bash
|
||||
kubectl get pods -l run=yanginx -o yaml | head -n 25
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
These pods are owned by a ReplicaSet named yanginx-xxxxxxxxxx.
|
||||
|
||||
---
|
||||
|
||||
## Listing objects with their owners
|
||||
|
||||
- This is a good opportunity to try the `custom-columns` output!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Show all pods with their owners:
|
||||
```bash
|
||||
kubectl get pod -o custom-columns=\
|
||||
NAME:.metadata.name,\
|
||||
OWNER-KIND:.metadata.ownerReferences[0].kind,\
|
||||
OWNER-NAME:.metadata.ownerReferences[0].name
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
Note: the `custom-columns` option should be one long option (without spaces),
|
||||
so the lines should not be indented (otherwise the indentation will insert spaces).
|
||||
|
||||
---
|
||||
|
||||
## Deletion policy
|
||||
|
||||
- When deleting an object through the API, three policies are available:
|
||||
|
||||
- foreground (API call returns after all dependents are deleted)
|
||||
|
||||
- background (API call returns immediately; dependents are scheduled for deletion)
|
||||
|
||||
- orphan (the dependents are not deleted)
|
||||
|
||||
- When deleting an object with `kubectl`, this is selected with `--cascade`:
|
||||
|
||||
- `--cascade=true` deletes all dependent objects (default)
|
||||
|
||||
- `--cascade=false` orphans dependent objects
|
||||
|
||||
---
|
||||
|
||||
## What happens when an object is deleted
|
||||
|
||||
- It is removed from the list of owners of its dependents
|
||||
|
||||
- If, for one of these dependents, the list of owners becomes empty ...
|
||||
|
||||
- if the policy is "orphan", the object stays
|
||||
|
||||
- otherwise, the object is deleted
|
||||
|
||||
---
|
||||
|
||||
## Orphaning pods
|
||||
|
||||
- We are going to delete the Deployment and Replica Set that we created
|
||||
|
||||
- ... without deleting the corresponding pods!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Delete the Deployment:
|
||||
```bash
|
||||
kubectl delete deployment -l run=yanginx --cascade=false
|
||||
```
|
||||
|
||||
- Delete the Replica Set:
|
||||
```bash
|
||||
kubectl delete replicaset -l run=yanginx --cascade=false
|
||||
```
|
||||
|
||||
- Check that the pods are still here:
|
||||
```bash
|
||||
kubectl get pods
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## When and why would we have orphans?
|
||||
|
||||
- If we remove an owner and explicitly instruct the API to orphan dependents
|
||||
|
||||
(like on the previous slide)
|
||||
|
||||
- If we change the labels on a dependent, so that it's not selected anymore
|
||||
|
||||
(e.g. change the `run: yanginx` in the pods of the previous example)
|
||||
|
||||
- If a deployment tool that we're using does these things for us
|
||||
|
||||
- If there is a serious problem within API machinery or other components
|
||||
|
||||
(i.e. "this should not happen")
|
||||
|
||||
---
|
||||
|
||||
## Finding orphan objects
|
||||
|
||||
- We're going to output all pods in JSON format
|
||||
|
||||
- Then we will use `jq` to keep only the ones *without* an owner
|
||||
|
||||
- And we will display their name
|
||||
|
||||
.exercise[
|
||||
|
||||
- List all pods that *do not* have an owner:
|
||||
```bash
|
||||
kubectl get pod -o json | jq -r "
|
||||
.items[]
|
||||
| select(.metadata.ownerReferences|not)
|
||||
| .metadata.name"
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Deleting orphan pods
|
||||
|
||||
- Now that we can list orphan pods, deleting them is easy
|
||||
|
||||
.exercise[
|
||||
|
||||
- Add `| xargs kubectl delete pod` to the previous command:
|
||||
```bash
|
||||
kubectl get pod -o json | jq -r "
|
||||
.items[]
|
||||
| select(.metadata.ownerReferences|not)
|
||||
| .metadata.name" | xargs kubectl delete pod
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
As always, the [documentation](https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/) has useful extra information and pointers.
|
||||
@@ -1,683 +0,0 @@
|
||||
# Highly available Persistent Volumes
|
||||
|
||||
- How can we achieve true durability?
|
||||
|
||||
- How can we store data that would survive the loss of a node?
|
||||
|
||||
--
|
||||
|
||||
- We need to use Persistent Volumes backed by highly available storage systems
|
||||
|
||||
- There are many ways to achieve that:
|
||||
|
||||
- leveraging our cloud's storage APIs
|
||||
|
||||
- using NAS/SAN systems or file servers
|
||||
|
||||
- distributed storage systems
|
||||
|
||||
--
|
||||
|
||||
- We are going to see one distributed storage system in action
|
||||
|
||||
---
|
||||
|
||||
## Our test scenario
|
||||
|
||||
- We will set up a distributed storage system on our cluster
|
||||
|
||||
- We will use it to deploy a SQL database (PostgreSQL)
|
||||
|
||||
- We will insert some test data in the database
|
||||
|
||||
- We will disrupt the node running the database
|
||||
|
||||
- We will see how it recovers
|
||||
|
||||
---
|
||||
|
||||
## Portworx
|
||||
|
||||
- Portworx is a *commercial* persistent storage solution for containers
|
||||
|
||||
- It works with Kubernetes, but also Mesos, Swarm ...
|
||||
|
||||
- It provides [hyper-converged](https://en.wikipedia.org/wiki/Hyper-converged_infrastructure) storage
|
||||
|
||||
(=storage is provided by regular compute nodes)
|
||||
|
||||
- We're going to use it here because it can be deployed on any Kubernetes cluster
|
||||
|
||||
(it doesn't require any particular infrastructure)
|
||||
|
||||
- We don't endorse or support Portworx in any particular way
|
||||
|
||||
(but we appreciate that it's super easy to install!)
|
||||
|
||||
---
|
||||
|
||||
## A useful reminder
|
||||
|
||||
- We're installing Portworx because we need a storage system
|
||||
|
||||
- If you are using AKS, EKS, GKE ... you already have a storage system
|
||||
|
||||
(but you might want another one, e.g. to leverage local storage)
|
||||
|
||||
- If you have setup Kubernetes yourself, there are other solutions available too
|
||||
|
||||
- on premises, you can use a good old SAN/NAS
|
||||
|
||||
- on a private cloud like OpenStack, you can use e.g. Cinder
|
||||
|
||||
- everywhere, you can use other systems, e.g. Gluster, StorageOS
|
||||
|
||||
---
|
||||
|
||||
## Portworx requirements
|
||||
|
||||
- Kubernetes cluster ✔️
|
||||
|
||||
- Optional key/value store (etcd or Consul) ❌
|
||||
|
||||
- At least one available block device ❌
|
||||
|
||||
---
|
||||
|
||||
## The key-value store
|
||||
|
||||
- In the current version of Portworx (1.4) it is recommended to use etcd or Consul
|
||||
|
||||
- But Portworx also has beta support for an embedded key/value store
|
||||
|
||||
- For simplicity, we are going to use the latter option
|
||||
|
||||
(but if we have deployed Consul or etcd, we can use that, too)
|
||||
|
||||
---
|
||||
|
||||
## One available block device
|
||||
|
||||
- Block device = disk or partition on a disk
|
||||
|
||||
- We can see block devices with `lsblk`
|
||||
|
||||
(or `cat /proc/partitions` if we're old school like that!)
|
||||
|
||||
- If we don't have a spare disk or partition, we can use a *loop device*
|
||||
|
||||
- A loop device is a block device actually backed by a file
|
||||
|
||||
- These are frequently used to mount ISO (CD/DVD) images or VM disk images
|
||||
|
||||
---
|
||||
|
||||
## Setting up a loop device
|
||||
|
||||
- We are going to create a 10 GB (empty) file on each node
|
||||
|
||||
- Then make a loop device from it, to be used by Portworx
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create a 10 GB file on each node:
|
||||
```bash
|
||||
for N in $(seq 1 5); do ssh node$N sudo truncate --size 10G /portworx.blk; done
|
||||
```
|
||||
(If SSH asks to confirm host keys, enter `yes` each time.)
|
||||
|
||||
- Associate the file to a loop device on each node:
|
||||
```bash
|
||||
for N in $(seq 1 5); do ssh node$N sudo losetup /dev/loop0 /portworx.blk; done
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Installing Portworx
|
||||
|
||||
- To install Portworx, we need to go to https://install.portworx.com/
|
||||
|
||||
- This website will ask us a bunch of questoins about our cluster
|
||||
|
||||
- Then, it will generate a YAML file that we should apply to our cluster
|
||||
|
||||
--
|
||||
|
||||
- Or, we can just apply that YAML file directly (it's in `k8s/portworx.yaml`)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Install Portworx:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/portworx.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Generating a custom YAML file
|
||||
|
||||
If you want to generate a YAML file tailored to your own needs, the easiest
|
||||
way is to use https://install.portworx.com/.
|
||||
|
||||
FYI, this is how we obtained the YAML file used earlier:
|
||||
```
|
||||
KBVER=$(kubectl version -o json | jq -r .serverVersion.gitVersion)
|
||||
BLKDEV=/dev/loop0
|
||||
curl https://install.portworx.com/1.4/?kbver=$KBVER&b=true&s=$BLKDEV&c=px-workshop&stork=true&lh=true
|
||||
```
|
||||
If you want to use an external key/value store, add one of the following:
|
||||
```
|
||||
&k=etcd://`XXX`:2379
|
||||
&k=consul://`XXX`:8500
|
||||
```
|
||||
... where `XXX` is the name or address of your etcd or Consul server.
|
||||
|
||||
---
|
||||
|
||||
## Waiting for Portworx to be ready
|
||||
|
||||
- The installation process will take a few minutes
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check out the logs:
|
||||
```bash
|
||||
stern -n kube-system portworx
|
||||
```
|
||||
|
||||
- Wait until it gets quiet
|
||||
|
||||
(you should see `portworx service is healthy`, too)
|
||||
|
||||
<!--
|
||||
```longwait PX node status reports portworx service is healthy```
|
||||
```keys ^C```
|
||||
-->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Dynamic provisioning of persistent volumes
|
||||
|
||||
- We are going to run PostgreSQL in a Stateful set
|
||||
|
||||
- The Stateful set will specify a `volumeClaimTemplate`
|
||||
|
||||
- That `volumeClaimTemplate` will create Persistent Volume Claims
|
||||
|
||||
- Kubernetes' [dynamic provisioning](https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/) will satisfy these Persistent Volume Claims
|
||||
|
||||
(by creating Persistent Volumes and binding them to the claims)
|
||||
|
||||
- The Persistent Volumes are then available for the PostgreSQL pods
|
||||
|
||||
---
|
||||
|
||||
## Storage Classes
|
||||
|
||||
- It's possible that multiple storage systems are available
|
||||
|
||||
- Or, that a storage system offers multiple tiers of storage
|
||||
|
||||
(SSD vs. magnetic; mirrored or not; etc.)
|
||||
|
||||
- We need to tell Kubernetes *which* system and tier to use
|
||||
|
||||
- This is achieved by creating a Storage Class
|
||||
|
||||
- A `volumeClaimTemplate` can indicate which Storage Class to use
|
||||
|
||||
- It is also possible to mark a Storage Class as "default"
|
||||
|
||||
(it will be used if a `volumeClaimTemplate` doesn't specify one)
|
||||
|
||||
---
|
||||
|
||||
## Our default Storage Class
|
||||
|
||||
This is our Storage Class (in `k8s/storage-class.yaml`):
|
||||
|
||||
```yaml
|
||||
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"
|
||||
```
|
||||
|
||||
- It says "use Portworx to create volumes"
|
||||
|
||||
- It tells Portworx to "keep 2 replicas of these volumes"
|
||||
|
||||
- It marks the Storage Class as being the default one
|
||||
|
||||
---
|
||||
|
||||
## 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`
|
||||
|
||||
- It defines a Stateful set
|
||||
|
||||
- With a `volumeClaimTemplate` requesting a 1 GB volume
|
||||
|
||||
- That volume will be mounted to `/var/lib/postgresql`
|
||||
|
||||
- There is another little detail: we enable the `stork` scheduler
|
||||
|
||||
- The `stork` scheduler is optional (it's specific to Portworx)
|
||||
|
||||
- It helps the Kubernetes scheduler to colocate the pod with its volume
|
||||
|
||||
(see [this blog post](https://portworx.com/stork-storage-orchestration-kubernetes/) for more details about that)
|
||||
|
||||
---
|
||||
|
||||
.small[
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgres
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
serviceName: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
schedulerName: stork
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:10.5
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
name: postgres
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: postgres
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
```
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Creating the Stateful set
|
||||
|
||||
- Before applying the YAML, watch what's going on with `kubectl get events -w`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Apply that YAML:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/postgres.yaml
|
||||
```
|
||||
|
||||
<!-- ```hide kubectl wait pod postgres-0 --for condition=ready``` -->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Testing our PostgreSQL pod
|
||||
|
||||
- We will use `kubectl exec` to get a shell in the pod
|
||||
|
||||
- Good to know: we need to use the `postgres` user in the pod
|
||||
|
||||
.exercise[
|
||||
|
||||
- Get a shell in the pod, as the `postgres` user:
|
||||
```bash
|
||||
kubectl exec -ti postgres-0 su postgres
|
||||
```
|
||||
|
||||
<!--
|
||||
autopilot prompt detection expects $ or # at the beginning of the line.
|
||||
```wait postgres@postgres```
|
||||
```keys PS1="\u@\h:\w\n\$ "```
|
||||
```keys ^J```
|
||||
-->
|
||||
|
||||
- Check that default databases have been created correctly:
|
||||
```bash
|
||||
psql -l
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(This should show us 3 lines: postgres, template0, and template1.)
|
||||
|
||||
---
|
||||
|
||||
## Inserting data in PostgreSQL
|
||||
|
||||
- We will create a database and populate it with `pgbench`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create a database named `demo`:
|
||||
```bash
|
||||
createdb demo
|
||||
```
|
||||
|
||||
- Populate it with `pgbench`:
|
||||
```bash
|
||||
pgbench -i -s 10 demo
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
- The `-i` flag means "create tables"
|
||||
|
||||
- The `-s 10` flag means "create 10 x 100,000 rows"
|
||||
|
||||
---
|
||||
|
||||
## Checking how much data we have now
|
||||
|
||||
- The `pgbench` tool inserts rows in table `pgbench_accounts`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check that the `demo` base exists:
|
||||
```bash
|
||||
psql -l
|
||||
```
|
||||
|
||||
- Check how many rows we have in `pgbench_accounts`:
|
||||
```bash
|
||||
psql demo -c "select count(*) from pgbench_accounts"
|
||||
```
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
|
||||
]
|
||||
|
||||
(We should see a count of 1,000,000 rows.)
|
||||
|
||||
---
|
||||
|
||||
## Find out which node is hosting the database
|
||||
|
||||
- We can find that information with `kubectl get pods -o wide`
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the node running the database:
|
||||
```bash
|
||||
kubectl get pod postgres-0 -o wide
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
We are going to disrupt that node.
|
||||
|
||||
--
|
||||
|
||||
By "disrupt" we mean: "disconnect it from the network".
|
||||
|
||||
---
|
||||
|
||||
## Disconnect the node
|
||||
|
||||
- We will use `iptables` to block all traffic exiting the node
|
||||
|
||||
(except SSH traffic, so we can repair the node later if needed)
|
||||
|
||||
.exercise[
|
||||
|
||||
- SSH to the node to disrupt:
|
||||
```bash
|
||||
ssh `nodeX`
|
||||
```
|
||||
|
||||
- Allow SSH traffic leaving the node, but block all other traffic:
|
||||
```bash
|
||||
sudo iptables -I OUTPUT -p tcp --sport 22 -j ACCEPT
|
||||
sudo iptables -I OUTPUT 2 -j DROP
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Check that the node is disconnected
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check that the node can't communicate with other nodes:
|
||||
```
|
||||
ping node1
|
||||
```
|
||||
|
||||
- Logout to go back on `node1`
|
||||
|
||||
<!-- ```keys ^D``` -->>
|
||||
|
||||
- Watch the events unfolding with `kubectl get events -w` and `kubectl get pods -w`
|
||||
|
||||
]
|
||||
|
||||
- It will take some time for Kubernetes to mark the node as unhealthy
|
||||
|
||||
- Then it will attempt to reschedule the pod to another node
|
||||
|
||||
- In about a minute, our pod should be up and running again
|
||||
|
||||
---
|
||||
|
||||
## Check that our data is still available
|
||||
|
||||
- We are going to reconnect to the (new) pod and check
|
||||
|
||||
.exercise[
|
||||
|
||||
- Get a shell on the pod:
|
||||
```bash
|
||||
kubectl exec -ti postgres-0 su postgres
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait postgres@postgres```
|
||||
```keys PS1="\u@\h:\w\n\$ "```
|
||||
```keys ^J```
|
||||
-->
|
||||
|
||||
- Check the number of rows in the `pgbench_accounts` table:
|
||||
```bash
|
||||
psql demo -c "select count(*) from pgbench_accounts"
|
||||
```
|
||||
|
||||
<!-- ```keys ^D``` -->
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Double-check that the pod has really moved
|
||||
|
||||
- Just to make sure the system is not bluffing!
|
||||
|
||||
.exercise[
|
||||
|
||||
- Look at which node the pod is now running on
|
||||
```bash
|
||||
kubectl get pod postgres-0 -o wide
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Re-enable the node
|
||||
|
||||
- Let's fix the node that we disconnected from the network
|
||||
|
||||
.exercise[
|
||||
|
||||
- SSH to the node:
|
||||
```bash
|
||||
ssh `nodeX`
|
||||
```
|
||||
|
||||
- Remove the iptables rule blocking traffic:
|
||||
```bash
|
||||
sudo iptables -D OUTPUT 2
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## A few words about this PostgreSQL setup
|
||||
|
||||
- In a real deployment, you would want to set a password
|
||||
|
||||
- This can be done by creating a `secret`:
|
||||
```
|
||||
kubectl create secret generic postgres \
|
||||
--from-literal=password=$(base64 /dev/urandom | head -c16)
|
||||
```
|
||||
|
||||
- And then passing that secret to the container:
|
||||
```yaml
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres
|
||||
key: password
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Troubleshooting Portworx
|
||||
|
||||
- If we need to see what's going on with Portworx:
|
||||
```
|
||||
PXPOD=$(kubectl -n kube-system get pod -l name=portworx -o json |
|
||||
jq -r .items[0].metadata.name)
|
||||
kubectl -n kube-system exec $PXPOD -- /opt/pwx/bin/pxctl status
|
||||
```
|
||||
|
||||
- We can also connect to Lighthouse (a web UI)
|
||||
|
||||
- check the port with `kubectl -n kube-system get svc px-lighthouse`
|
||||
|
||||
- connect to that port
|
||||
|
||||
- the default login/password is `admin/Password1`
|
||||
|
||||
- then specify `portworx-service` as the endpoint
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Removing Portworx
|
||||
|
||||
- Portworx provides a storage driver
|
||||
|
||||
- It needs to place itself "above" the Kubelet
|
||||
|
||||
(it installs itself straight on the nodes)
|
||||
|
||||
- To remove it, we need to do more than just deleting its Kubernetes resources
|
||||
|
||||
- It is done by applying a special label:
|
||||
```
|
||||
kubectl label nodes --all px/enabled=remove --overwrite
|
||||
```
|
||||
|
||||
- Then removing a bunch of local files:
|
||||
```
|
||||
sudo chattr -i /etc/pwx/.private.json
|
||||
sudo rm -rf /etc/pwx /opt/pwx
|
||||
```
|
||||
|
||||
(on each node where Portworx was running)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Dynamic provisioning without a provider
|
||||
|
||||
- What if we want to use Stateful sets without a storage provider?
|
||||
|
||||
- We will have to create volumes manually
|
||||
|
||||
(by creating Persistent Volume objects)
|
||||
|
||||
- These volumes will be automatically bound with matching Persistent Volume Claims
|
||||
|
||||
- We can use local volumes (essentially bind mounts of host directories)
|
||||
|
||||
- Of course, these volumes won't be available in case of node failure
|
||||
|
||||
- Check [this blog post](https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/) for more information and gotchas
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
The Portworx installation tutorial, and the PostgreSQL example,
|
||||
were inspired by [Portworx examples on Katacoda](https://katacoda.com/portworx/scenarios/), in particular:
|
||||
|
||||
- [installing Portworx on Kubernetes](https://www.katacoda.com/portworx/scenarios/deploy-px-k8s)
|
||||
|
||||
(with adapatations to use a loop device and an embedded key/value store)
|
||||
|
||||
- [persistent volumes on Kubernetes using Portworx](https://www.katacoda.com/portworx/scenarios/px-k8s-vol-basic)
|
||||
|
||||
(with adapatations to specify a default Storage Class)
|
||||
|
||||
- [HA PostgreSQL on Kubernetes with Portworx](https://www.katacoda.com/portworx/scenarios/px-k8s-postgres-all-in-one)
|
||||
|
||||
(with adaptations to use a Stateful Set and simplify PostgreSQL's setup)
|
||||
@@ -1,486 +0,0 @@
|
||||
# Collecting metrics with Prometheus
|
||||
|
||||
- Prometheus is an open-source monitoring system including:
|
||||
|
||||
- multiple *service discovery* backends to figure out which metrics to collect
|
||||
|
||||
- a *scraper* to collect these metrics
|
||||
|
||||
- an efficient *time series database* to store these metrics
|
||||
|
||||
- a specific query language (PromQL) to query these time series
|
||||
|
||||
- an *alert manager* to notify us according to metrics values or trends
|
||||
|
||||
- We are going to deploy it on our Kubernetes cluster and see how to query it
|
||||
|
||||
---
|
||||
|
||||
## Why Prometheus?
|
||||
|
||||
- We don't endorse Prometheus more or less than any other system
|
||||
|
||||
- It's relatively well integrated within the Cloud Native ecosystem
|
||||
|
||||
- It can be self-hosted (this is useful for tutorials like this)
|
||||
|
||||
- It can be used for deployments of varying complexity:
|
||||
|
||||
- one binary and 10 lines of configuration to get started
|
||||
|
||||
- all the way to thousands of nodes and millions of metrics
|
||||
|
||||
---
|
||||
|
||||
## Exposing metrics to Prometheus
|
||||
|
||||
- Prometheus obtains metrics and their values by querying *exporters*
|
||||
|
||||
- An exporter serves metrics over HTTP, in plain text
|
||||
|
||||
- This is was the *node exporter* looks like:
|
||||
|
||||
http://demo.robustperception.io:9100/metrics
|
||||
|
||||
- Prometheus itself exposes its own internal metrics, too:
|
||||
|
||||
http://demo.robustperception.io:9090/metrics
|
||||
|
||||
- If you want to expose custom metrics to Prometheus:
|
||||
|
||||
- serve a text page like these, and you're good to go
|
||||
|
||||
- libraries are available in various languages to help with quantiles etc.
|
||||
|
||||
---
|
||||
|
||||
## How Prometheus gets these metrics
|
||||
|
||||
- The *Prometheus server* will *scrape* URLs like these at regular intervals
|
||||
|
||||
(by default: every minute; can be more/less frequent)
|
||||
|
||||
- If you're worried about parsing overhead: exporters can also use protobuf
|
||||
|
||||
- The list of URLs to scrape (the *scrape targets*) is defined in configuration
|
||||
|
||||
---
|
||||
|
||||
## Defining scrape targets
|
||||
|
||||
This is maybe the simplest configuration file for Prometheus:
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
```
|
||||
|
||||
- In this configuration, Prometheus collects its own internal metrics
|
||||
|
||||
- A typical configuration file will have multiple `scrape_configs`
|
||||
|
||||
- In this configuration, the list of targets is fixed
|
||||
|
||||
- A typical configuration file will use dynamic service discovery
|
||||
|
||||
---
|
||||
|
||||
## Service discovery
|
||||
|
||||
This configuration file will leverage existing DNS `A` records:
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- ...
|
||||
- job_name: 'node'
|
||||
dns_sd_configs:
|
||||
- names: ['api-backends.dc-paris-2.enix.io']
|
||||
type: 'A'
|
||||
port: 9100
|
||||
```
|
||||
|
||||
- In this configuration, Prometheus resolves the provided name(s)
|
||||
|
||||
(here, `api-backends.dc-paris-2.enix.io`)
|
||||
|
||||
- Each resulting IP address is added as a target on port 9100
|
||||
|
||||
---
|
||||
|
||||
## Dynamic service discovery
|
||||
|
||||
- In the DNS example, the names are re-resolved at regular intervals
|
||||
|
||||
- As DNS records are created/updated/removed, scrape targets change as well
|
||||
|
||||
- Existing data (previously collected metrics) is not deleted
|
||||
|
||||
- Other service discovery backends work in a similar fashion
|
||||
|
||||
---
|
||||
|
||||
## Other service discovery mechanisms
|
||||
|
||||
- Prometheus can connect to e.g. a cloud API to list instances
|
||||
|
||||
- Or to the Kubernetes API to list nodes, pods, services ...
|
||||
|
||||
- Or a service like Consul, Zookeeper, etcd, to list applications
|
||||
|
||||
- The resulting configurations files are *way more complex*
|
||||
|
||||
(but don't worry, we won't need to write them ourselves)
|
||||
|
||||
---
|
||||
|
||||
## Time series database
|
||||
|
||||
- We could wonder, "why do we need a specialized database?"
|
||||
|
||||
- One metrics data point = metrics ID + timestamp + value
|
||||
|
||||
- With a classic SQL or noSQL data store, that's at least 160 bits of data + indexes
|
||||
|
||||
- Prometheus is way more efficient, without sacrificing performance
|
||||
|
||||
(it will even be gentler on the I/O subsystem since it needs to write less)
|
||||
|
||||
FIXME link to Goutham's talk
|
||||
|
||||
---
|
||||
|
||||
## Running Prometheus on our cluster
|
||||
|
||||
We need to:
|
||||
|
||||
- Run the Prometheus server in a pod
|
||||
|
||||
(using e.g. a Deployment to ensure that it keeps running)
|
||||
|
||||
- Expose the Prometheus server web UI (e.g. with a NodePort)
|
||||
|
||||
- Run the *node exporter* on each node (with a Daemon Set)
|
||||
|
||||
- Setup a Service Account so that Prometheus can query the Kubernetes API
|
||||
|
||||
- Configure the Prometheus server
|
||||
|
||||
(storing the configuration in a Config Map for easy updates)
|
||||
|
||||
---
|
||||
|
||||
## Helm Charts to the rescue
|
||||
|
||||
- To make our lives easier, we are going to use a Helm Chart
|
||||
|
||||
- The Helm Chart will take care of all the steps explained above
|
||||
|
||||
(including some extra features that we don't need, but won't hurt)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: install Helm
|
||||
|
||||
- If we already installed Helm earlier, these commands won't break anything
|
||||
|
||||
.exercice[
|
||||
|
||||
- Install Tiller (Helm's server-side component) on our cluster:
|
||||
```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
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Step 2: install Prometheus
|
||||
|
||||
- Skip this if we already installed Prometheus earlier
|
||||
|
||||
(in doubt, check with `helm list`)
|
||||
|
||||
.exercice[
|
||||
|
||||
- Install Prometheus on our cluster:
|
||||
```bash
|
||||
helm install stable/prometheus \
|
||||
--set server.service.type=NodePort \
|
||||
--set server.persistentVolume.enabled=false
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
The provided flags:
|
||||
|
||||
- expose the server web UI (and API) on a NodePort
|
||||
|
||||
- use an ephemeral volume for metrics storage
|
||||
<br/>
|
||||
(instead of requesting a Persistent Volume through a Persistent Volume Claim)
|
||||
|
||||
---
|
||||
|
||||
## Connecting to the Prometheus web UI
|
||||
|
||||
- Let's connect to the web UI and see what we can do
|
||||
|
||||
.exercise[
|
||||
|
||||
- Figure out the NodePort that was allocated to the Prometheus server:
|
||||
```bash
|
||||
kubectl get svc | grep prometheus-server
|
||||
```
|
||||
|
||||
- With your browser, connect to that port
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Querying some metrics
|
||||
|
||||
- This is easy ... if you are familiar with PromQL
|
||||
|
||||
.exercise[
|
||||
|
||||
- Click on "Graph", and in "expression", paste the following:
|
||||
```
|
||||
sum by (instance) (
|
||||
irate(
|
||||
container_cpu_usage_seconds_total{
|
||||
pod_name=~"worker.*"
|
||||
}[5m]
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
- Click on the blue "Execute" button and on the "Graph" tab just below
|
||||
|
||||
- We see the cumulated CPU usage of worker pods for each node
|
||||
<br/>
|
||||
(if we just deployed Prometheus, there won't be much data to see, though)
|
||||
|
||||
---
|
||||
|
||||
## Getting started with PromQL
|
||||
|
||||
- We can't learn PromQL in just 5 minutes
|
||||
|
||||
- But we can cover the basics to get an idea of what is possible
|
||||
|
||||
(and have some keywords and pointers)
|
||||
|
||||
- We are going to break down the query above
|
||||
|
||||
(building it one step at a time)
|
||||
|
||||
---
|
||||
|
||||
## Graphing one metric across all tags
|
||||
|
||||
This query will show us CPU usage across all containers:
|
||||
```
|
||||
container_cpu_usage_seconds_total
|
||||
```
|
||||
|
||||
- The suffix of the metrics name tells us:
|
||||
|
||||
- the unit (seconds of CPU)
|
||||
|
||||
- that it's the total used since the container creation
|
||||
|
||||
- Since it's a "total", it is an increasing quantity
|
||||
|
||||
(we need to compute the derivative if we want e.g. CPU % over time)
|
||||
|
||||
- We see that the metrics retrieved have *tags* attached to them
|
||||
|
||||
---
|
||||
|
||||
## Selecting metrics with tags
|
||||
|
||||
This query will show us only metrics for worker containers:
|
||||
```
|
||||
container_cpu_usage_seconds_total{pod_name=~"worker.*"}
|
||||
```
|
||||
|
||||
- The `=~` operator allows regex matching
|
||||
|
||||
- We select all the pods with a name starting with `worker`
|
||||
|
||||
(it would be better to use labels to select pods; more on that later)
|
||||
|
||||
- The result is a smaller set of containers
|
||||
|
||||
---
|
||||
|
||||
## Transforming counters in rates
|
||||
|
||||
This query will show us CPU usage % instead of total seconds used:
|
||||
```
|
||||
100*irate(container_cpu_usage_seconds_total{pod_name=~"worker.*"}[5m])
|
||||
```
|
||||
|
||||
- The [`irate`](https://prometheus.io/docs/prometheus/latest/querying/functions/#irate) operator computes the "per-second instant rate of increase"
|
||||
|
||||
- `rate` is similar but allows decreasing counters and negative values
|
||||
|
||||
- with `irate`, if a counter goes back to zero, we don't get a negative spike
|
||||
|
||||
- The `[5m]` tells how far to look back if there is a gap in the data
|
||||
|
||||
- And we multiply with `100*` to get CPU % usage
|
||||
|
||||
---
|
||||
|
||||
## Aggregation operators
|
||||
|
||||
This query sums the CPU usage per node:
|
||||
```
|
||||
sum by (instance) (
|
||||
irate(container_cpu_usage_seconds_total{pod_name=~"worker.*"}[5m])
|
||||
)
|
||||
```
|
||||
|
||||
- `instance` corresponds to the node on which the container is running
|
||||
|
||||
- `sum by (instance) (...)` computes the sum for each instance
|
||||
|
||||
- Note: all the other tags are collapsed
|
||||
|
||||
(in other words, the resulting graph only shows the `instance` tag)
|
||||
|
||||
- PromQL supports many more [aggregation operators](https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators)
|
||||
|
||||
---
|
||||
|
||||
## What kind of metrics can we collect?
|
||||
|
||||
- Node metrics (related to physical or virtual machines)
|
||||
|
||||
- Container metrics (resource usage per container)
|
||||
|
||||
- Databases, message queues, load balancers, ...
|
||||
|
||||
(check out this [list of exporters](https://prometheus.io/docs/instrumenting/exporters/)!)
|
||||
|
||||
- Instrumentation (=deluxe `printf` for our code)
|
||||
|
||||
- Business metrics (customers served, revenue, ...)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Node metrics
|
||||
|
||||
- CPU, RAM, disk usage on the whole node
|
||||
|
||||
- Total number of processes running, and their states
|
||||
|
||||
- Number of open files, sockets, and their states
|
||||
|
||||
- I/O activity (disk, network), per operation or volume
|
||||
|
||||
- Physical/hardware (when applicable): temperature, fan speed ...
|
||||
|
||||
- ... and much more!
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Container metrics
|
||||
|
||||
- Similar to node metrics, but not totally identical
|
||||
|
||||
- RAM breakdown will be different
|
||||
|
||||
- active vs inactive memory
|
||||
- some memory is *shared* between containers, and accounted specially
|
||||
|
||||
- I/O activity is also harder to track
|
||||
|
||||
- async writes can cause deferred "charges"
|
||||
- some page-ins are also shared between containers
|
||||
|
||||
For details about container metrics, see:
|
||||
<br/>
|
||||
http://jpetazzo.github.io/2013/10/08/docker-containers-metrics/
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Application metrics
|
||||
|
||||
- Arbitrary metrics related to your application and business
|
||||
|
||||
- System performance: request latency, error rate ...
|
||||
|
||||
- Volume information: number of rows in database, message queue size ...
|
||||
|
||||
- Business data: inventory, items sold, revenue ...
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Detecting scrape targets
|
||||
|
||||
- Prometheus can leverage Kubernetes service discovery
|
||||
|
||||
(with proper configuration)
|
||||
|
||||
- Services or pods can be annotated with:
|
||||
|
||||
- `prometheus.io/scrape: true` to enable scraping
|
||||
- `prometheus.io/port: 9090` to indicate the port number
|
||||
- `prometheus.io/path: /metrics` to indicate the URI (`/metrics` by default)
|
||||
|
||||
- Prometheus will detect and scrape these (without needing a restart or reload)
|
||||
|
||||
---
|
||||
|
||||
## Querying labels
|
||||
|
||||
- What if we want to get metrics for containers belong to pod tagged `worker`?
|
||||
|
||||
- The cAdvisor exporter does not give us Kubernetes labels
|
||||
|
||||
- Kubernetes labels are exposed through another exporter
|
||||
|
||||
- We can see Kubernetes labels through metrics `kube_pod_labels`
|
||||
|
||||
(each container appears as a time series with constant value of `1`)
|
||||
|
||||
- Prometheus *kind of* supports "joins" between time series
|
||||
|
||||
- But only if the names of the tags match exactly
|
||||
|
||||
---
|
||||
|
||||
## Unfortunately ...
|
||||
|
||||
- The cAdvisor exporter uses tag `pod_name` for the name of a pod
|
||||
|
||||
- The Kubernetes service endpoints exporter uses tag `pod` instead
|
||||
|
||||
- And this is why we can't have nice things
|
||||
|
||||
- See [Prometheus issue #2204](https://github.com/prometheus/prometheus/issues/2204) for the rationale
|
||||
|
||||
([this comment](https://github.com/prometheus/prometheus/issues/2204#issuecomment-261515520) in particular if you want a workaround involving relabeling)
|
||||
|
||||
- Then see [this blog post](https://www.robustperception.io/exposing-the-software-version-to-prometheus) or [this other one](https://www.weave.works/blog/aggregating-pod-resource-cpu-memory-usage-arbitrary-labels-prometheus/) to see how to perform "joins"
|
||||
|
||||
- There is a good chance that the situation will improve in the future
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user