Deploying with



Deploying with

2 5


deploying-with-docker

Deploying With Docker

On Github willdurand-slides / deploying-with-docker

Deploying with

William Durand - April 17th, 2015

Docker In A Nutshell

Containers

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.

Containers vs VMs

Docker

  • Developed by Docker.inc (formerly DotCloud)
  • Open platform for developing, shipping,and running applications
  • Use libcontainer as execution driver

Docker's Architecture

Try It!

The Plan

  • Simple solution to deploy my application
  • Idempotent and reliable process
  • No need for rollbacks
  • Targetting zero downtime
  • Single host

Methodology

Twelve Factors

  • One codebase tracked in revision control,many deploys
  • Store config in the environment
  • Keep development, staging, and productionas similar as possible

12factor.net

Infrastructure

Services

Overall Architecture

Application's fig.yml

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 Only Containers

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

Application's Dockerfile

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" ]

DNS

example.org www.example.org

Configured at the DNS level. Faster than making this redirection using the load balancer.

SSL

Hipache listens on ports 80 and 443.

Patched version of Hipache to redirectHTTP traffic to HTTPS. Sort of SSL offloading.

willdurand/docker-hipache

Deploy

1. Build Artifact

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>

2. Build Container (From Artifact)

docker build -t myapp:latest .

3. Push It

docker push

Or whatever that makes the image available on the production server.

4. Retrieve Running Containers

RUNNING_CIDS=$(docker ps | grep web | cut -d ' ' -f 1)

5. Run The New Container

fig run -d --no-deps --rm web

Booting the container may take a few seconds, wait.

6. Register It As Backend

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

7. Unregister Previous Backends

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.

8. Stop Previous Containers

For each container id CID in $RUNNING_CIDS, do:

docker stop <CID>

9. Tag A Release (And Profit!)

Database Migrations

This Is My Vision

  • Use Phinx (or Flyway) to write your migration scripts
  • Put you migration scripts under version control
  • Test your migration scripts
  • Applying your migration scripts should be done asynchronously (i.e. not tied to a deployment)
  • This is tricky (i.e. not easy)
  • You need a plan!

Adding A New Column/Table

Prepare your migration before modifying your code Apply your migration Update your code

Modifying A Column Type

Create a new column with the new type as a migration Apply your migration Create a migration to populate the new column Apply your second migration Update your code to use this new column (your code should deal with both columns) Rename the first column as "deprecated", and the new one using the original name Remove hacks introduced in 5

Renaming A Column/Table

Create a new column or table with the new name rather than "renaming" it Apply your migration Update your code to use the new column/table but keep using the former Create a migration to populate the new column/table Apply your migration Update your code to use the new column/table Rename the former column/table as "deprecated" Remove hacks introduced in 3

Deleting A Column/Table

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.

Making Changes On The Data

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 online

Benchmarking

Like Apache Bench, but better!

tarekziade/boom

1. Run boom

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.

2. Deploy

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.

3. Monitor

Elasticsearch, Logstash, Kibana + Logstash Forwarder (lumberjack)

willdurand/docker-elk

willdurand/docker-logstash-forwarder

Testing

wercker.yml

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

     - ...

1. Build Artifact

- script:
    name: build artifact
    code: |
      FAB_DIST_DIR="$WERCKER_OUTPUT_DIR" \
      bin/fabric build:commit="$WERCKER_GIT_COMMIT"

2. Build a Docker Image

- script:
    name: build image
    code: |
      cd "$WERCKER_OUTPUT_DIR/build-$WERCKER_GIT_COMMIT"
      fig -f "$WERCKER_SOURCE_DIR/fig.yml.dist" build web

3. Run a Container

- 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

4. Run Tests \o/

- 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

Smoke Testing

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

RSpec + httparty = ♥

Thank You.

Questions?

Bonus

Fig & Environments

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

    # ...
0