William Durand - April 17th, 2015
A container is an operating-system level virtualization method that provides a completely isolated environment, simulating a closed system running on a single host.
chroot, OpenVZ, Jail, LXC, etc.
web: build: . # No support for `env-file` yet... # See: https://github.com/docker/fig/pull/665 environment: - SYMFONY_ENV=dev - SYMFONY_DEBUG=1 - SYMFONY__MEMCACHED_HOST=memcached - SYMFONY__MEMCACHED_PORT=11211 - SYMFONY__SESSION_MEMCACHED_PREFIX=session - SYMFONY__SESSION_MEMCACHED_EXPIRE=604800 links: - memcached - database
Data, logs, etc. MUST live in data only containers:
# fig.yml redis: image: redis volumes_from: - dataredis dataredis: image: busybox volumes: - /data
Wanna backup?
docker run --rm \ --volumes-from dataredis \ -v $(pwd):/backup \ busybox \ tar cvf /backup/dataredis.tgz /data
FROM debian:wheezy MAINTAINER William Durand <will@drnd.me> ENV DEBIAN_FRONTEND noninteractive # Let APT know about HHVM repo... RUN apt-get update -y RUN apt-get install -y nginx hhvm supervisor # Configure Nginx, HHVM, Supervisor here... ADD . /app WORKDIR /app EXPOSE 80 CMD [ "/usr/bin/supervisord" ]
example.org www.example.org
Configured at the DNS level. Faster than making this redirection using the load balancer.
Hipache listens on ports 80 and 443.
Patched version of Hipache to redirectHTTP traffic to HTTPS. Sort of SSL offloading.
git archive --format tar --prefix myapp/ <SHA> | (cd /tmp && tar xf -)
Protip: use a Docker container to run build tools:
cd /tmp/myapp docker run --rm -v `pwd`:/srv buildtools composer install docker run --rm -v `pwd`:/srv buildtools bundle install docker run --rm -v `pwd`:/srv buildtools bower install rm <USELESS/TEMP FILES>
docker build -t myapp:latest .
docker push
Or whatever that makes the image available on the production server.
RUNNING_CIDS=$(docker ps | grep web | cut -d ' ' -f 1)
fig run -d --no-deps --rm web
Booting the container may take a few seconds, wait.
IP=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' <CID>)
fig run --rm rediscli rpush frontend:www.example.com "http://$IP:80"
# fig.yml rediscli: image: redis entrypoint: [ redis-cli, -h, redis ] links: - redis
For each container id CID in $RUNNING_CIDS, do:
IP=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' <CID>) fig run --rm rediscli lrem frontend:www.example.com 0 "http://$IP:80"
New backend is up and serves requests. However, previous backends may still process requests. Wait.
For each container id CID in $RUNNING_CIDS, do:
docker stop <CID>
Rename it rather than deleting it. Wait for a maintenance party to delete *_deprecated columns and/or tables.
Protip: when marking a column/table as deprecated, add the date too: _deprecated_2015_01_01.
Avoid it as long as you can, but sometimes,there are no other alternatives.
This implies a maintenance mode: be careful!
Put your service in maintenance mode Apply your changes Put your service back onlineLike Apache Bench, but better!
boom https://www.example.org/ -n 2000 -c 50 Server Software: nginx/1.2.1 Running GET https://www.example.org/ Running 2000 times per 50 workers. [====================================================>.] 99% Done -------- Results -------- Successful calls 2000 Total time 137.1827 s Average 6.7114 s Fastest 2.3701 s Slowest 9.4738 s Amplitude 7.1037 s -------- Status codes -------- Code 200 2000 times.
fab deploy --hide=running,stdout,stderr ---> Deploying branch "awesome-feature" ---> Building artifact ---> Artifact id is: "build-8fe47fc" ---> Pushing artifact to the server ---> Building container ---> Finding running containers ---> Starting new container ---> Waiting while container is booting ---> Registering new backend to Hipache ---> Unregistering the previous backends from Hipache ---> Waiting before stopping the previous backends Done.
I use Fabric.
Elasticsearch, Logstash, Kibana + Logstash Forwarder (lumberjack)
box: wercker-labs/docker build: steps: - script: name: install Fig code: | curl -L https://.../fig-`uname -s`-`uname -m` > fig ; chmod +x fig sudo mv fig /usr/local/bin/fig - ...
- script: name: build artifact code: | FAB_DIST_DIR="$WERCKER_OUTPUT_DIR" \ bin/fabric build:commit="$WERCKER_GIT_COMMIT"
- script: name: build image code: | cd "$WERCKER_OUTPUT_DIR/build-$WERCKER_GIT_COMMIT" fig -f "$WERCKER_SOURCE_DIR/fig.yml.dist" build web
- script: name: run container code: | cd "$WERCKER_OUTPUT_DIR/build-$WERCKER_GIT_COMMIT" fig -f "$WERCKER_SOURCE_DIR/fig.yml.dist" run -d web sleep 20
- script: name: run test suite code: | export IP=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}'\ "$(docker ps -q | head -n 1)") echo "IP address is: $IP" | tee "$WERCKER_REPORT_MESSAGE_FILE" bin/test
require "httparty" describe "The web server" do before(:all) do ip = ENV["IP"] or 'localhost' @response = HTTParty.get( "http://#{ip}:80", :basic_auth => { :username => "privatedemo", :password => "please" } ) end it "should return a 200 status code" do @response.code.should == 200 end end
Keep development, staging, and production as similar as possibleX. Dev/prod parity — 12 Factors
# fig.yml.dist web: # In PRODUCTION, the line below MUST be COMMENTED: build: . # In PRODUCTION, the line below must be UNCOMMENTED: # image: myapp # ...