Asmir Mustafic
(and a couple of other tools)
PHP CE 2019 - Dresden - Germany
Cancelled
Berlin
<?php
echo "Hello world";
Windows Notepad
C:\\Project\index.php
Apache + mod_php + MySQL
installed on laptop
ftp> put index.php
More $$$, more users, compute power, environments, tests, configurations....
Docker
$ docker build ...
Build
Run
$ docker run ...
FROM debian:9.1
RUN apt install nginx -y
COPY nginx.conf /etc/nginx/
USER www-data
CMD ["nginx"]
Small
Self-contained
Small
Self-contained
BUILD
RELEASE
RUN
PUSH
RELEASE
<?php
echo "Hello world";
public/index.php
PHP + NGINX
docker run ...
docker build ...
docker rm ...
docker ls ...
docker volume ...
docker images ...
...
dockerd
~REST API
Default LISTEN socket unix:///var/run/docker.sock
LISTEN tcp://1.2.3.4.5:2375
export DOCKER_HOST="tcp://1.2.3.4:2376"
docker run ...
docker build ...
docker rm ...
docker ls ...
docker volume ...
docker images ...
...
docker login
docker logout
Client only
Build
Run
Build
Run
Build
Run
Build
Run
services:
web:
image: goetas/web
command: nginx -f
build:
dockerfile: docker/nginx/Dockerfile
php:
image: goetas/php
build: ./apps/docker/php
cache:
image: redis
docker-compose.yml
FROM php:7.1.6-fpm
# ...
php Dockerfile
FROM nginx:1.17.2
# ...
nginx Dockerfile
$ docker-compose build
$ docker-compose up
PUSH
get dependencies
run tests
prepare images
(and push images to the registry)
(running build means just running some bash commands)
cp .ci.env .env
export TAG=${CI_GIT_SHA}
export BUILD_ID=${CI_BUILD_ID}
Variables
version: '3.4'
services:
php:
image: goetas/php:${TAG}
build:
context: .
dockerfile: docker/php/Dockerfile
args:
BUILD_ID: ${BUILD_ID}
docker-compose.build.yml
version: '3.4'
services:
php:
image: goetas/php:d0be2dc421be4fcd0172e5afceea3970e2f3d940
build:
context: .
dockerfile: docker/php/Dockerfile
args:
BUILD_ID: 878
git clone ...
# build images
docker-compose build
# install composer dependencies
docker-compose run --no-deps --rm php \
composer install \
--no-progress --no-interaction \
--no-scripts --no-plugins
# re-build images with vendor
docker-compose build
Dependencies
Successfully built d9dd746fcd75
Successfully tagged goetas/php:d0be2dc421be4f
Successfully built d9dd746fcd75
Successfully tagged goetas/web:d0be2dc421be4f
# tests
docker-compose run --no-deps --rm php \
vendor/bin/phpunit
Run tests
You have tests, right?
PHPUnit 8.3.0 by Sebastian Bergmann and contributors.
...
Time: 0 seconds, Memory: 3.25Mb
OK (3 tests, 4 assertions)
# login to docker registry
echo Y | docker login -u "$USR" -p "$PASS"
# push images
docker-compose push
Push images to registry
Pushing web (goetas/web)...
The push refers to a repository [docker.io/goetas/web]
latest: digest: sha256:b3dff3e82a0e12b54a10ed083b65fd size: 1786
Pushing php (goetas/php)...
The push refers to a repository [docker.io/goetas/php]
latest: digest: sha256:af07f1e4e3d1a311f35a92d5336dc2 size: 7836
BUILD
Build
Run
Build
Run
Build
Run
Build
Run
Build
Run
Build
Run
Build
Run
$ docker swarm init
Swarm initialized: current node (bvz81updecsj6wj)
is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-3pu6hszjas19xyp7ghgos \
172.17.0.2:2377
To add a manager to this swarm,
run 'docker swarm join-token manager'
and follow the instructions.
Create a cluster (and a manager node)
$ docker swarm join \
--token SWMTKN-1-3pu6hszjas19xyp7ghgos \
172.17.0.2:2377
This node joined a swarm as a manager.
Add a worker node to a cluster
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
38ciaotwjuri sw-wrk1 Ready Active
e216jshn25ck * sw-man1 Ready Active Leader
List nodes from a manager
TLS authentication
export DOCKER_HOST="tcp://1.2.3.4:2376"
export DOCKER_TLS_VERIFY="1"
export DOCKER_CERT_PATH="/etc/docker/server.pem"
$ docker node ls
RELEASE
RELEASE
RUN
docker stack deploy -c docker-stack.yml my_app
version: '3.4'
services:
web:
image: goetas/web
php:
image: goetas/php
cache:
image: redis
docker-stack.yml
export DOCKER_HOST="tcp://1.2.3.4:2375"
docker stack deploy -c docker-stack.yml my_app
export DOCKER_HOST="tcp://1.2.3.4:2375"
docker stack deploy -c docker-stack.yml my_app-staging
Multiple environments
COMPOSE_FILE=a.yml:b.yml:c.yml
docker-compose -f a.yml -f b.yml -f c.yml build
COMPOSE_FILE=a.yml:b.yml:c.yml docker-compose build
.env
docker-compose build
version: '3.4'
services:
web:
image: goetas/web:${TAG}
build:
context: .
dockerfile: docker/nginx/Dockerfile
php:
image: goetas/php:${TAG}
build:
context: .
dockerfile: docker/php/Dockerfile
args:
BUILD_ID: ${BUILD_ID}
docker-compose.build.yml
(only what is necessary for the build)
version: '3.4'
services:
db:
image: postgres:11.4
cache:
image: redis:5.0.5-alpine
docker-compose.yml
Shared configs for all developers
version: '3.4'
services:
web:
ports:
- "8080:80" # 127.0.0.16:80:80
volumes:
- .:/app
php:
volumes:
- .:/app
db:
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data: {}
docker-compose.dev.dist.yml
(copy into docker-compose.dev.yml, ignored by GIT)
Customizations for developers
version: '3.4'
services:
php:
environment:
SECRET_API_KEY: ${TEST_API_KEY}
SMPT_HOST: smtp
volumes:
- ./:/rd
smtp:
image: tophfr/mailcatcher
docker-compose.ci.yml
Customizations for CI and Tests
COMPOSE_FILE=
docker-compose.build.yml: \
docker-compose.yml: \
docker-compose.dev.yml \
// Windows use ;
// COMPOSE_FILE=
// docker-compose.build.yml; \
// docker-compose.yml; \
// docker-compose.dev.yml \
TAG=dev
BUILD_ID=unknown
.dist.env
(copy into .env, ignored by GIT!)
COMPOSE_FILE=
docker-compose.build.yml: \
docker-compose.yml: \
docker-compose.ci.yml
.ci.env
(copy into .env on CI)
FROM php:7.1.6-fpm
# ...
docker/php/Dockerfile
FROM nginx:1.17.2
# ...
docker/nginx/Dockerfile
cp .dist.env .env
cp docker-compose.dev.dist.yml docker-compose.dev.yml
docker-compose up
node_modules/
var/
vendor/*
.git
.dockerignore
.gitignore
.gitlab-ci.yml
.php_cs.cache
docker-compose*
README.md
.dockerignore
Docker is client server
docker build .
FROM php:7.3.6-fpm
# ...
Exact versions
FROM php:7.3.6-fpm
RUN apt-get update
RUN apt-get install -y nginx
Repeatable builds
FROM php:7.3.6-fpm
RUN apt-get update && \
apt-get install -y nginx
FROM php:7.3.6-fpm
RUN apt-get update \
&& apt-get install -y git
COPY index.php index.php
COPY src src
Cache friendly and fast
FROM php:7.3.6-fpm
COPY src src
COPY index.php index.php
RUN apt-get update \
&& apt-get install -y git
Cache origin
version: '3.4'
services:
php:
image: goetas/php:${TAG}
build:
context: .
dockerfile: docker/php/Dockerfile
cache-from:
- goetas/php:${TAG}
- goetas/php:${CACHE_TAG}
IMAGES=$(
docker-compose config
| python \
-c 'import sys,yaml,json; json.dump(yaml.load(sys.stdin), sys.stdout)'
| jq -r '([.services[].build.cache_from]|flatten) | del(.[]|nulls)[]'
| sort
| uniq
)
for i in $IMAGES; do docker pull "$i" || true; done
Download caches
FROM nginx:1.14.0
RUN apt update && \
apt install vim -y
RUN rm -rf /var/cache/apt
Small images
FROM nginx:1.14.0
RUN apt update \
&& apt install vim -y \
&& rm -rf /var/cache/apt
Build arguments
version: '3.4'
services:
php:
image: goetas/php:${TAG}
build:
context: .
dockerfile: docker/php/Dockerfile
args:
BUILD_ID: ${BUILD_ID}
FROM php:7.3.6-fpm
ARG BUILD_ID unknown
RUN echo "$BUILD_ID" > config/verson.txt
FROM nginx:1.14.0
COPY docker/nginx/www.conf /www.conf
COPY docker/nginx/entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Environment variables everywhere
#!/bin/bash -e
envsubst < /www.conf > /etc/nginx/nginx.conf
/entrypoint.sh
server {
listen ${HTTP_PORT};
root /var/www/app/public;
}
/www.conf
version: '3.4'
services:
php:
image: goetas/php:${TAG}
logging:
driver: gelf
options:
gelf-address: "udp://logging.myserver.com:12302"
none, local, json-file, syslogm journald, gelf, fluentd, awslogs, splunk, etwlogs, gcplogs, logentries
One container on each node of the cluster
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
mode: global
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
replica: 5
Run 5 copies of the PHP container
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
placement:
constraints:
- engine.labels.data-center == eu-west-1
- node.role == frontend
Decide on which node place the containers
$ docker node update --add-label "role=frontend" 2l9kajahy2
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
resources:
limits:
cpus: '0.50'
memory: '50M'
reservations:
cpus: '0.25'
memory: '20M'
Limit/Reserve CPU or memory
version: '3.4'
services:
web:
image: goetas/web:${TAG}
healthcheck:
test: curl -f -sS http://localhost/health
interval: 20s
timeout: 5s
retries: 2
start_period: 2s
Check container health
Restart on failure
version: '3.4'
services:
web:
image: goetas/web:${TAG}
secrets:
- ssh_private_key
- ssl_certificate
ssh_private_key:
file: ./my_key.rsa
ssl_certificate:
external: true
Forward secret credentials/configurations
/run/secrets/ssh_private_key and /run/secrets/ssl_certificate
crated inside the containers
$ docker secret create ssl_certificate my.crt
version: '3.4'
services:
web:
image: goetas/web:${TAG}
deploy:
replicas: 6
update_config:
parallelism: 2
delay: '10s'
order: start-first
rollback_config:
parallelism: 2
delay: '20s'
order: start-first
Timing for container updates/rollback