Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Container ecosystem

Slide 17

Slide 17 text

Cloud Native Landscape

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Hi, I’m Thijs

Slide 20

Slide 20 text

I’m @ThijsFeryn on Twitter

Slide 21

Slide 21 text

I’m an Evangelist At

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

A container image is a lightweight, stand-alone, executable package of a piece of software that includes everything needed to run it: code, runtime, system tools, system libraries, settings. Available for both Linux and Windows based apps, containerized software will always run the same, regardless of the environment. Containers isolate software from its surroundings, for example differences between development and staging environments and help reduce conflicts between teams running different software on the same infrastructure.

Slide 25

Slide 25 text

A “new” approach to virtualization

Slide 26

Slide 26 text

1 Linux kernel Multiple isolated Linux systems

Slide 27

Slide 27 text

✓Kernel namespaces ✓Chroots ✓CGroups ✓Unprivileged users Linux containers

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Why?

Slide 30

Slide 30 text

Lots of moving parts

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Local development

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Continuous Integration Continuous Delivery

Slide 35

Slide 35 text

Because we can

Slide 36

Slide 36 text

Build image Store image in local registry Run image Docker build Docker run Dockerfile

Slide 37

Slide 37 text

Build image Push image to registry Pull image from registry Run image Registry Docker build Docker push Docker pull Docker run Dockerfile

Slide 38

Slide 38 text

Build images in “infrastructure as code” style

Slide 39

Slide 39 text

LAMP stack

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

Dockerfile

Slide 42

Slide 42 text

FROM debian:stretch WORKDIR /var/www/html RUN apt-get update \ && apt-get install -y apache2 php7.0 libapache2-mod-php7.0 CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"] EXPOSE 80

Slide 43

Slide 43 text

$ docker build -t my-wordpress .

Slide 44

Slide 44 text

$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE my-wordpress latest 07c4a68f6f40 26 seconds ago 235MB

Slide 45

Slide 45 text

$ docker run --name some-wordpress -p 80:80 -d -v "$PWD/../code:/var/www/html" my-wordpress:latest

Slide 46

Slide 46 text

$ docker tag my-wordpress thijsferyn/wordpress $ docker push thijsferyn/wordpress

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

https://hub.docker.com/_/php/

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

$ docker run --name some-wordpress -p 80:80 -d -v "$PWD/../code:/var/www/html" php:apache

Slide 51

Slide 51 text

Fatal error: Uncaught Error: Call to undefined function mysql_connect() in /var/www/html/wp- includes/wp-db.php:1564 Stack trace: #0 /var/www/ html/wp-includes/wp-db.php(592): wpdb->db_connect() #1 /var/www/html/wp-includes/load.php(404): wpdb- >__construct('wordpress', 'wordpress', 'wordpress', 'db:3306') #2 /var/www/html/wp-settings.php(106): require_wp_db() #3 /var/www/html/wp-config.php(101): require_once('/var/www/html/w...') #4 /var/www/html/ wp-load.php(37): require_once('/var/www/html/w...') #5 /var/www/html/wp-blog-header.php(13): require_once('/var/www/html/w...') #6 /var/www/html/ index.php(17): require('/var/www/html/w...') #7 {main} thrown in /var/www/html/wp-includes/wp- db.php on line 1564

Slide 52

Slide 52 text

https://hub.docker.com/_/wordpress/

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

$ docker run --name some-wordpress -p 80:80 -d wordpress:latest

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

$ docker run --name some-wordpress -p 80:80 -d -v "$PWD/../code:/var/www/html" wordpress:latest

Slide 57

Slide 57 text

$ docker ps CONTAINER ID IMAGE PORTS NAMES 81aedc3da1a7 wordpress:latest 0.0.0.0:80->80/tcp some-wordpress

Slide 58

Slide 58 text

$ docker stop some-wordpress $ docker rm some-wordpress

Slide 59

Slide 59 text

What about

Slide 60

Slide 60 text

https://hub.docker.com/_/mysql/

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

$ docker run -e MYSQL_ROOT_PASSWORD=somewordpress -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wordpress --name some-mysql -p 3306:3306 -d -v "$PWD/../db/data:/var/lib/mysql" -v "$PWD/../db/sql:/docker- entrypoint-initdb.d" mysql:5.7

Slide 63

Slide 63 text

Why not run services in “infrastructure as code” style?

Slide 64

Slide 64 text

Docker Compose

Slide 65

Slide 65 text

version: '3.3' services: db: image: mysql:5.7 volumes: - ../db/data:/var/lib/mysql - ../db/sql:/docker-entrypoint-initdb.d environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest volumes: - ../code:/var/www/html ports: - "80:80" docker-compose.yml

Slide 66

Slide 66 text

$ docker-compose up -d Creating docker_db_1 ... done Creating docker_wordpress_1 ... done

Slide 67

Slide 67 text

$ docker-compose logs -f

Slide 68

Slide 68 text

Attaching to docker_wordpress_1, docker_db_1 wordpress_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.18.0.3. Set the 'ServerName' directive globally to suppress this message wordpress_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.18.0.3. Set the 'ServerName' directive globally to suppress this message wordpress_1 | [Mon Feb 26 16:14:54.498385 2018] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.25 (Debian) PHP/7.2.2 configured -- resuming normal operations wordpress_1 | [Mon Feb 26 16:14:54.498549 2018] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND' db_1 | 2018-02-26T16:14:54.133816Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use -- explicit_defaults_for_timestamp server option (see documentation for more details). db_1 | 2018-02-26T16:14:54.146003Z 0 [Note] mysqld (mysqld 5.7.21) starting as process 1 ... db_1 | 2018-02-26T16:14:54.152600Z 0 [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive db_1 | 2018-02-26T16:14:54.154772Z 0 [Note] InnoDB: PUNCH HOLE support available db_1 | 2018-02-26T16:14:54.154830Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins db_1 | 2018-02-26T16:14:54.154839Z 0 [Note] InnoDB: Uses event mutexes db_1 | 2018-02-26T16:14:54.154848Z 0 [Note] InnoDB: GCC builtin __atomic_thread_fence() is used for memory barrier db_1 | 2018-02-26T16:14:54.154856Z 0 [Note] InnoDB: Compressed tables use zlib 1.2.3 db_1 | 2018-02-26T16:14:54.154863Z 0 [Note] InnoDB: Using Linux native AIO db_1 | 2018-02-26T16:14:54.155374Z 0 [Note] InnoDB: Number of pools: 1 db_1 | 2018-02-26T16:14:54.155508Z 0 [Note] InnoDB: Using CPU crc32 instructions db_1 | 2018-02-26T16:14:54.157423Z 0 [Note] InnoDB: Initializing buffer pool, total size = 128M, instances = 1, chunk size = 128M db_1 | 2018-02-26T16:14:54.164871Z 0 [Note] InnoDB: Completed initialization of buffer pool db_1 | 2018-02-26T16:14:54.167964Z 0 [Note] InnoDB: If the mysqld execution user is authorized, page cleaner thread priority can be changed. See the man page of setpriority(). db_1 | 2018-02-26T16:14:54.201936Z 0 [Note] InnoDB: Highest supported file format is Barracuda. db_1 | 2018-02-26T16:14:54.389809Z 0 [Note] InnoDB: Creating shared tablespace for temporary tables db_1 | 2018-02-26T16:14:54.391076Z 0 [Note] InnoDB: Setting file './ibtmp1' size to 12 MB. Physically writing the file full; Please wait ...

Slide 69

Slide 69 text

$ docker-compose top docker_db_1 PID USER TIME COMMAND ---------------------------- 8190 999 0:00 mysqld docker_wordpress_1 PID USER TIME COMMAND ----------------------------------------- 8410 0 0:00 apache2 -DFOREGROUND 8570 33 0:00 apache2 -DFOREGROUND 8571 33 0:00 apache2 -DFOREGROUND 8572 33 0:00 apache2 -DFOREGROUND 8573 33 0:00 apache2 -DFOREGROUND 8574 33 0:00 apache2 -DFOREGROUND

Slide 70

Slide 70 text

$ docker-compose down

Slide 71

Slide 71 text

version: '3.3' services: db: image: mysql:5.7 volumes: - ../db/data:/var/lib/mysql - ../db/sql:/docker-entrypoint-initdb.d environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest volumes: - ../code:/var/www/html ports: - "80:80" cli: depends_on: - wordpress image: wordpress:cli volumes: - ../code:/var/www/html entrypoint: wp redis: image: redis:4

Slide 72

Slide 72 text

MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest volumes: - ../code:/var/www/html ports: - "80:80" cli: depends_on: - wordpress image: wordpress:cli volumes: - ../code:/var/www/html entrypoint: wp redis: image: redis:4

Slide 73

Slide 73 text

$ docker-compose run --rm cli plugin list +------------------+----------+--------+---------+ | name | status | update | version | +------------------+----------+--------+---------+ | akismet | inactive | none | 4.0.3 | | hello | inactive | none | 1.6 | | redis-cache | inactive | none | 1.3.5 | | wordpress-seo | inactive | none | 6.3.1 | | object-cache.php | dropin | none | | +------------------+----------+--------+---------+

Slide 74

Slide 74 text

$ docker-compose run --rm cli plugin activate redis-cache Plugin 'redis-cache' activated. Success: Activated 1 of 1 plugins.

Slide 75

Slide 75 text

Continuous integration/delivery

Slide 76

Slide 76 text

test "`curl -s localhost | pup 'title text{}'`" = "Docker – Just another WordPress sites" Parse DOM elements Call Docker container String assertion

Slide 77

Slide 77 text

✓PHPUnit ✓CodeCeption ✓Behat More advanced tools

Slide 78

Slide 78 text

Deployment

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

image: wordpress:latest stages: - test services: - name: mysql:5.7 alias: db variables: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress test:integration: before_script: - apt-get update - apt-get install -y git golang-go mysql-client - GOPATH="$HOME/go" go get github.com/ericchiang/pup - cat sql/wordpress.sql | mysql --user="$MYSQL_USER" --password="$MYSQL_PASSWORD" --host=db "$MYSQL_DATABASE" - cp -R wp-* index.php .htaccess /var/www/html - apachectl start stage: test script: - test "`curl -s http://localhost | $HOME/go/bin/pup 'title text{}'`" = "Docker – Just another WordPress site" allow_failure: false .gitlab-ci.yml Uses Docker for job runners

Slide 83

Slide 83 text

$ git push origin master

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Deployment

Slide 88

Slide 88 text

SFTP SCP RSYNC GIT

Slide 89

Slide 89 text

$ docker run --name some-wordpress -p 80:80 -d wordpress:latest

Slide 90

Slide 90 text

Custom images & private registry

Slide 91

Slide 91 text

FROM debian:stretch WORKDIR /var/www/html RUN apt-get update \ && apt-get install -y apache2 php7.0 libapache2-mod-php7.0 php7.0-mysql \ && rm index.html \ CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"] EXPOSE 80

Slide 92

Slide 92 text

$ docker login git.domain.com:4567 $ docker build -t git.domain.com:4567/thijsferyn/wordpress-docker . $ docker push git.domain.com:4567/thijsferyn/wordpress-docker

Slide 93

Slide 93 text

$ docker run --name=my-wordpress -p 80:80 -d -v "$PWD:/var/www/ html" git.domain.com:4567/thijsferyn/wordpress-docker

Slide 94

Slide 94 text

Might be a slippery slope

Slide 95

Slide 95 text

Container can die

Slide 96

Slide 96 text

Multiple services battling for ports Is port forwarding the answer?

Slide 97

Slide 97 text

Service discovery

Slide 98

Slide 98 text

High availability

Slide 99

Slide 99 text

1 container seems useless

Slide 100

Slide 100 text

Orchestration

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

Test locally

Slide 103

Slide 103 text

$ docker stack deploy -c docker-compose.yml wordpress Stack wordpress was created Waiting for the stack to be stable and running... - Service redis has one container running - Service db has one container running - Service wordpress has one container running Stack wordpress is stable and running

Slide 104

Slide 104 text

$ docker service ls ID NAME REPLICAS IMAGE PORTS jvyxfqthji4v wordpress_db 1/1 mysql:5.7 8fuv862hdqea wordpress_redis 1/1 redis:4 es3x49n9hvx2 wordpress_wordpress 1/1 wordpress:latest *:80->80/tcp

Slide 105

Slide 105 text

$ docker stack ls NAME SERVICES wordpress 3

Slide 106

Slide 106 text

$ docker stack rm wordpress

Slide 107

Slide 107 text

Supported in edge version. The way forward

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

$ docker stack deploy -c docker-compose.yml wordpress Stack wordpress was created Waiting for the stack to be stable and running... - Service redis has one container running - Service db has one container running - Service wordpress has one container running Stack wordpress is stable and running

Slide 110

Slide 110 text

MySQL pod MySQL container MySQL service Wordpress pod Wordpress container Wordpress service Wordpress PVC MySQL PVC MySQL Volume Wordpress Volume Ingress Internet

Slide 111

Slide 111 text

$ kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE db 1 1 1 1 36s redis 1 1 1 1 36s wordpress 1 1 1 1 36s

Slide 112

Slide 112 text

$ kubectl get po NAME READY STATUS RESTARTS AGE db-69d9b64b67-x76b4 1/1 Running 0 1m redis-854d68cff7-bwdsg 1/1 Running 0 1m wordpress-895dd96f9-kcdgq 1/1 Running 0 1m

Slide 113

Slide 113 text

$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE db ClusterIP None 55555/TCP 1m kubernetes ClusterIP 10.96.0.1 443/TCP 7d redis ClusterIP None 55555/TCP 1m wordpress ClusterIP None 55555/TCP 1m wordpress-published LoadBalancer 10.98.180.147 localhost 80:31351/TCP 1m

Slide 114

Slide 114 text

Write our own deployment

Slide 115

Slide 115 text

MySQL

Slide 116

Slide 116 text

apiVersion: v1 kind: Service metadata: name: wordpress-mysql labels: app: wordpress spec: ports: - port: 3306 selector: app: wordpress tier: mysql clusterIP: None

Slide 117

Slide 117 text

--- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi

Slide 118

Slide 118 text

apiVersion: apps/v1 kind: Deployment metadata: name: wordpress-mysql labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: mysql strategy: type: Recreate template: metadata: labels: app: wordpress tier: mysql spec: containers: - image: mysql:5.7 name: mysql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef:

Slide 119

Slide 119 text

spec: containers: - image: mysql:5.7 name: mysql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-root-pass key: password - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password - name: MYSQL_DATABASE value: wordpress - name: MYSQL_USER value: wordpress ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: mysql-pv-claim

Slide 120

Slide 120 text

Wordpress

Slide 121

Slide 121 text

--- apiVersion: v1 kind: Service metadata: name: wordpress labels: app: wordpress spec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer

Slide 122

Slide 122 text

--- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: wp-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi

Slide 123

Slide 123 text

--- apiVersion: apps/v1 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:latest name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-mysql

Slide 124

Slide 124 text

spec: containers: - image: wordpress:latest name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-mysql - name: WORDPRESS_DB_USER value: wordpress - name: WORDPRESS_DB_NAME value: wordpress - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim

Slide 125

Slide 125 text

$ kubectl create secret generic mysql-pass --from-literal=password=wordpress $ kubectl create secret generic mysql-root-pass --from- literal=password=somewordpress

Slide 126

Slide 126 text

$ kubectl apply -f wordpress.yml service "wordpress-mysql" created persistentvolumeclaim "mysql-pv-claim" created deployment "wordpress-mysql" created service "wordpress" created persistentvolumeclaim "wp-pv-claim" created deployment "wordpress" created

Slide 127

Slide 127 text

$ kubectl get po NAME READY STATUS RESTARTS AGE wordpress-8d5dd96ff-7f9nx 0/1 ContainerCreating 0 3s wordpress-mysql-7b4c776bc8-lwz5f 0/1 ContainerCreating 0 4s $ kubectl get po NAME READY STATUS RESTARTS AGE wordpress-8d5dd96ff-gpqx5 1/1 Running 0 48s wordpress-mysql-7b4c776bc8-dzr9w 1/1 Running 0 49s

Slide 128

Slide 128 text

$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 443/TCP 7d wordpress LoadBalancer 10.111.50.221 80:30876/TCP 26s wordpress-mysql ClusterIP None 3306/TCP 26s

Slide 129

Slide 129 text

$ kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE wordpress 1 1 1 0 1s wordpress-mysql 1 1 1 0 2s $ kubectl scale deployment wordpress --replicas=2 deployment "wordpress" scaled $ kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE wordpress 2 2 2 2 13m wordpress-mysql 1 1 1 1 13m

Slide 130

Slide 130 text

No content

Slide 131

Slide 131 text

$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 443/TCP 7d wordpress LoadBalancer 10.111.50.221 80:30876/TCP 15m wordpress-mysql ClusterIP None 3306/TCP 15m $ minikube service --url wordpress http://192.168.64.3:30876 $ minikube service wordpress

Slide 132

Slide 132 text

$ minikube dashboard

Slide 133

Slide 133 text

No content

Slide 134

Slide 134 text

Deployment to production

Slide 135

Slide 135 text

$ kubectl config use-context production $ kubectl apply -f wordpress.yml

Slide 136

Slide 136 text

There’s more

Slide 137

Slide 137 text

Lots more

Slide 138

Slide 138 text

But that’s a story for another day

Slide 139

Slide 139 text

https://feryn.eu https://twitter.com/ThijsFeryn https://instagram.com/ThijsFeryn

Slide 140

Slide 140 text

No content