Asmir Mustafic
(and a couple of other tools)
PHP CE 2019 - Dresden - Germany
Cancelled
Me
Asmir Mustafic
Me
@goetas
- Twitter: @goetas_asmir
- Github: @goetas
- LinkedIn: @goetas
- WWW: goetas.com
Berlin
Community
- jms/serializer (maintainer)
- masterminds/html5 (maintainer)
- hautelook/templated-uri-bundle (maintainer)
- goetas-webservices/xsd2php (author)
- goetas-webservices/xsd-reader (author)
- goetas-webservices/soap-client (author)
- goetas/twital (author)
- PHP-FIG secretary
The past
1998-2000
PHP 3, 4
Code
<?php
echo "Hello world";Windows Notepad
C:\\Project\index.php
Dev environment
Apache + mod_php + MySQL
installed on laptop

Deploy
ftp> put index.phpInternet

Now
More $$$, more users, compute power, environments, tests, configurations....
Code
PHPStorm / Vi / Sublime
GIT / Mercurial
...
Dev environment


RAM RAM RAM RAM
Dev environment

Docker
Dockerfile
$ docker build ...Docker image
Container
Build
Run
$ docker run ...Docker
FROM debian:9.1
RUN apt install nginx -y
COPY nginx.conf /etc/nginx/
USER www-data
CMD ["nginx"]Containerized Applications
Small
Self-contained
12 factor
Small
Self-contained
Dev === Production
Ideal workflow





BUILD
RELEASE
RUN
PUSH
RELEASE
Ideal workflow





Development
<?php
echo "Hello world";public/index.php
PHP + NGINX
Remember
Docker is client server!
docker run ...
docker build ...
docker rm ...
docker ls ...
docker volume ...
docker images ...
...dockerd
~REST API
Default LISTEN socket unix:///var/run/docker.sock
Remember
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 logoutClient only
docker-compose

Docker
Dockerfile
Docker image
Container
Build
Run
Dockerfile
Docker image
Container
Build
Run
Docker Compose
Dockerfile
Docker image
Container
Build
Run
Dockerfile
Docker image
Container
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: redisdocker-compose.yml
FROM php:7.1.6-fpm
# ...
php Dockerfile
FROM nginx:1.17.2
# ...
nginx Dockerfile
$ docker-compose build$ docker-compose upIdeal workflow





PUSH
Done!
Ideal workflow





Build responsibilities
-
get dependencies
-
run tests
-
prepare images
(and push images to the registry)
Build
The CI server runs the build
(running build means just running some bash commands)
Build 0/3
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: 878git clone ...
Build 1/3
# 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 buildDependencies
Successfully built d9dd746fcd75
Successfully tagged goetas/php:d0be2dc421be4f
Successfully built d9dd746fcd75
Successfully tagged goetas/web:d0be2dc421be4fBuild 2/3
# tests
docker-compose run --no-deps --rm php \
vendor/bin/phpunitRun 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)Build 3/3
# 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: 7836Ideal workflow





BUILD
Done!
Ideal workflow





Docker Swarm

Docker
Dockerfile
Docker image
Container
Build
Run
Dockerfile
Docker image
Container
Build
Run
Docker Compose
Dockerfile
Docker image
Container
Build
Run
Dockerfile
Docker image
Container
Build
Run
Dockerfile
Docker image
Container
Build
Run
Docker Swarm
Dockerfile
Docker image
Container
Build
Run
Dockerfile
Docker image
Container
Build
Run





Docker Swarm
- simple
- part of docker
- uses docker-compose syntax
- 80% of use-cases
Docker Swarm
$ docker swarm initSwarm 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
$ 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 Swarm
$ docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
38ciaotwjuri sw-wrk1 Ready Active
e216jshn25ck * sw-man1 Ready Active LeaderList nodes from a manager
Docker Swarm
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 lsDone?
Ideal workflow





RELEASE
RELEASE
RUN
Step 5. Deploy
docker stack deploy -c docker-stack.yml my_appversion: '3.4'
services:
web:
image: goetas/web
php:
image: goetas/php
cache:
image: redisdocker-stack.yml
Step 5. Deploy
export DOCKER_HOST="tcp://1.2.3.4:2375"
docker stack deploy -c docker-stack.yml my_appexport DOCKER_HOST="tcp://1.2.3.4:2375"
docker stack deploy -c docker-stack.yml my_app-stagingMultiple environments
Done!
Container tips
Application tips
Stateless
- No data
- No logs
Disposability
- fast start
- fast shutdown
Communication
- only port communication, no shared folders
- no difference between 3rd party services and local services
Configurable
- configurations must be not part of the image, but provided at run-time
Concurrency
- must allow multiple instances of the same process
Docker-Compose tips
COMPOSE_FILE=a.yml:b.yml:c.ymldocker-compose -f a.yml -f b.yml -f c.yml buildMultiple files
COMPOSE_FILE=a.yml:b.yml:c.yml docker-compose build.env
docker-compose buildversion: '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 upBuild tips
Building images
node_modules/
var/
vendor/*
.git
.dockerignore
.gitignore
.gitlab-ci.yml
.php_cs.cache
docker-compose*
README.md.dockerignore
Docker is client server
docker build .Building images
FROM php:7.3.6-fpm
# ...Exact versions
Building images
FROM php:7.3.6-fpm
RUN apt-get update
RUN apt-get install -y nginxRepeatable builds
FROM php:7.3.6-fpm
RUN apt-get update && \
apt-get install -y nginxBuilding images
FROM php:7.3.6-fpm
RUN apt-get update \
&& apt-get install -y git
COPY index.php index.php
COPY src srcCache 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 gitBuilding images
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; doneDownload caches
Building images
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
Building images
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.txtBuilding images
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
Swarm tips
Logs
STDOUT / STDERR
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
Global services
One container on each node of the cluster
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
mode: globalScale services
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
replica: 5Run 5 copies of the PHP container
Container placement
version: '3.4'
services:
php:
image: goetas/php:${TAG}
deploy:
placement:
constraints:
- engine.labels.data-center == eu-west-1
- node.role == frontendDecide on which node place the containers
$ docker node update --add-label "role=frontend" 2l9kajahy2Resource usage
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
Health checks
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: 2sCheck container health
Restart on failure
Secrets
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: trueForward secret credentials/configurations
/run/secrets/ssh_private_key and /run/secrets/ssl_certificate
crated inside the containers
$ docker secret create ssl_certificate my.crtUpdate policy
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-firstTiming for container updates/rollback
Kubernetes?




Thank you!
- Twitter: @goetas_asmir
- Github: @goetas
- LinkedIn: @goetas
- WWW: goetas.com
Deploy your PHP app with Docker Swarm - PHPCE 2019
By Asmir Mustafic
Deploy your PHP app with Docker Swarm - PHPCE 2019
Setup your local environment, setup the remote server, push the code, done. Your application is ready and deployed. This talks is a walk-trough on how to setup a continuous integration and delivery pipeline to have your PHP code pushed right to production using modern CI tools and Docker Swarm.
- 1,884