Docker for WordPress developers - WordCamp Antwerp 2018

Docker for WordPress developers - WordCamp Antwerp 2018

Ca901ddcea38854b9783781c91fc87c9?s=128

Thijs Feryn

March 03, 2018
Tweet

Transcript

  1. None
  2. None
  3. None
  4. None
  5. None
  6. None
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. Container ecosystem

  17. Cloud Native Landscape

  18. None
  19. Hi, I’m Thijs

  20. I’m @ThijsFeryn on Twitter

  21. I’m an Evangelist At

  22. None
  23. None
  24. 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.
  25. A “new” approach to virtualization

  26. 1 Linux kernel Multiple isolated Linux systems

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

  28. None
  29. Why?

  30. Lots of moving parts

  31. None
  32. Local development

  33. None
  34. Continuous Integration Continuous Delivery

  35. Because we can

  36. Build image Store image in local registry Run image Docker

    build Docker run Dockerfile
  37. Build image Push image to registry Pull image from registry

    Run image Registry Docker build Docker push Docker pull Docker run Dockerfile
  38. Build images in “infrastructure as code” style

  39. LAMP stack

  40. None
  41. Dockerfile

  42. 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
  43. $ docker build -t my-wordpress .

  44. $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE my-wordpress

    latest 07c4a68f6f40 26 seconds ago 235MB
  45. $ docker run --name some-wordpress -p 80:80 -d -v "$PWD/../code:/var/www/html"

    my-wordpress:latest
  46. $ docker tag my-wordpress thijsferyn/wordpress $ docker push thijsferyn/wordpress

  47. None
  48. https://hub.docker.com/_/php/

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

    php:apache
  51. 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
  52. https://hub.docker.com/_/wordpress/

  53. None
  54. $ docker run --name some-wordpress -p 80:80 -d wordpress:latest

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

    wordpress:latest
  57. $ docker ps CONTAINER ID IMAGE PORTS NAMES 81aedc3da1a7 wordpress:latest

    0.0.0.0:80->80/tcp some-wordpress
  58. $ docker stop some-wordpress $ docker rm some-wordpress

  59. What about

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

  61. None
  62. $ 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
  63. Why not run services in “infrastructure as code” style?

  64. Docker Compose

  65. 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
  66. $ docker-compose up -d Creating docker_db_1 ... done Creating docker_wordpress_1

    ... done
  67. $ docker-compose logs -f

  68. 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 ...
  69. $ 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
  70. $ docker-compose down

  71. 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
  72. 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
  73. $ 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 | | +------------------+----------+--------+---------+
  74. $ docker-compose run --rm cli plugin activate redis-cache Plugin 'redis-cache'

    activated. Success: Activated 1 of 1 plugins.
  75. Continuous integration/delivery

  76. test "`curl -s localhost | pup 'title text{}'`" = "Docker

    – Just another WordPress sites" Parse DOM elements Call Docker container String assertion
  77. ✓PHPUnit ✓CodeCeption ✓Behat More advanced tools

  78. Deployment

  79. None
  80. None
  81. None
  82. 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
  83. $ git push origin master

  84. None
  85. None
  86. None
  87. Deployment

  88. SFTP SCP RSYNC GIT

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

  90. Custom images & private registry

  91. 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
  92. $ 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
  93. $ docker run --name=my-wordpress -p 80:80 -d -v "$PWD:/var/www/ html"

    git.domain.com:4567/thijsferyn/wordpress-docker
  94. Might be a slippery slope

  95. Container can die

  96. Multiple services battling for ports Is port forwarding the answer?

  97. Service discovery

  98. High availability

  99. 1 container seems useless

  100. Orchestration

  101. None
  102. Test locally

  103. $ 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
  104. $ 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
  105. $ docker stack ls NAME SERVICES wordpress 3

  106. $ docker stack rm wordpress

  107. Supported in edge version. The way forward

  108. None
  109. $ 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
  110. MySQL pod MySQL container MySQL service Wordpress pod Wordpress container

    Wordpress service Wordpress PVC MySQL PVC MySQL Volume Wordpress Volume Ingress Internet
  111. $ 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
  112. $ 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
  113. $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

    db ClusterIP None <none> 55555/TCP 1m kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d redis ClusterIP None <none> 55555/TCP 1m wordpress ClusterIP None <none> 55555/TCP 1m wordpress-published LoadBalancer 10.98.180.147 localhost 80:31351/TCP 1m
  114. Write our own deployment

  115. MySQL

  116. apiVersion: v1 kind: Service metadata: name: wordpress-mysql labels: app: wordpress

    spec: ports: - port: 3306 selector: app: wordpress tier: mysql clusterIP: None
  117. --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim labels: app:

    wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
  118. 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:
  119. 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
  120. Wordpress

  121. --- apiVersion: v1 kind: Service metadata: name: wordpress labels: app:

    wordpress spec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer
  122. --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: wp-pv-claim labels: app:

    wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
  123. --- 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
  124. 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
  125. $ kubectl create secret generic mysql-pass --from-literal=password=wordpress $ kubectl create

    secret generic mysql-root-pass --from- literal=password=somewordpress
  126. $ 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
  127. $ 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
  128. $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

    kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d wordpress LoadBalancer 10.111.50.221 <pending> 80:30876/TCP 26s wordpress-mysql ClusterIP None <none> 3306/TCP 26s
  129. $ 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
  130. None
  131. $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

    kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d wordpress LoadBalancer 10.111.50.221 <pending> 80:30876/TCP 15m wordpress-mysql ClusterIP None <none> 3306/TCP 15m $ minikube service --url wordpress http://192.168.64.3:30876 $ minikube service wordpress
  132. $ minikube dashboard

  133. None
  134. Deployment to production

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

  136. There’s more

  137. Lots more

  138. But that’s a story for another day

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

  140. None