Try This At Home

Building a Personal Docker Swarm

Matthew Clemente

A Familiar Story

  • Adobe ColdFusion 10 /11
  • Windows Server 2008
  • Microsoft IIS
  • Rackspace
  • FTP Deployments
  • Dev Server (in basement)

Every Conference

Looking for Direction

Legacy Infrastructure

Scriptable

Scalable

Containerized

12-Factor App

CI/CD

Microservices

Bret Fisher Resources

Bret Fisher

Learned a Lot

Still don't know how to do this.

¯\_(ツ)_/¯

You'll learn more on the first day of production than the previous two months.

Bret Fisher, Taking Docker to Production, DockerCon Europe 2017

Doing

is

Learning

All genuine learning comes through experience.

John Dewey, Experience & Education, 1938

How do I actually do this?

Don't tell me that it's possible without showing me how!

Goal

Learning By Doing
Concrete Examples

What This Talk Is Not:

  • Intro to Docker
  • Intro to Swarm
  • Comprehensive
  • "The Best Way"*
  • Starting point
  • Practical and concrete
  • Code Samples!
  • Necessarily limited

What This Talk Is:

Tooling and Services

How Do I...

  • Structure my project?
  • Minimize divergence between Dev and Prod?
  • Use environment variables?
  • Manage sensitive information?
  • Use a private image registry?
  • Tag images properly?
  • Add Lucee extensions?
  • Configure CI/CD for deployment?
  • Monitor with FusionReactor?
  • Handle sessions?
  • File a tax extension?

It can be done

ಠಿ_ಠ

Begin with the end in mind.

Stephen Covey, The 7 Habits of Highly Effective People, 1989

Project Structure

.
├── .env
├── .gitlab-ci.yml
├── .secrets
│   ├── cfml.admin.password.dev
│   └── cfml.admin.password.v1
├── app
│   ├── .CFConfig.json
│   ├── box.json
│   ├── server.json
│   └── wwwroot
│       └── index.cfm
├── build
│   ├── cfml
│   │   ├── Dockerfile
│   │   └── config
│   │       └── extensions
│   │           └── extension-loganalyzer-2.3.2.16.lex
│   └── deploy-secrets.sh
├── docker-compose.override.yml
├── docker-compose.prod.yml
└── docker-compose.yml

Sharing Configuration

.
├── docker-compose.override.yml
├── docker-compose.prod.yml
└── docker-compose.yml
$ docker-compose up

Minimize Divergence Between Dev and Prod

Sharing Configuration

$ docker-compose \
    -f docker-compose.yml \
    -f docker-compose.prod.yml \
    up
# Works if not dependent on Swarm features
docker stack deploy \
    -c docker-compose.yml \
    -c docker-compose.prod.yml \
    test
# For Swarm deployments

Minimize Divergence Between Dev and Prod

version: "3.7"

services:
  cfml:
    image: "registry.gitlab.com/${CI_PROJECT_NAMESPACE}/starter-swarm-cfml/cfml:${BUILD_TAG:-latest}"
    build:
      context: .
      dockerfile: ./build/cfml/Dockerfile
    environment:
      PORT: 8080
      SSL_PORT: 8443
      cfconfigfile: .CFConfig.json
      cfconfig_inspectTemplate: never
    secrets:
      - source: cfml.admin.password
        target: cfml.admin.password
    ports:
      - target: 8080
        published: 80
      - target: 8443
        published: 443

networks:
  internal:
    driver: overlay

secrets:
  cfml.admin.password:
    external: true
    name: cfml.admin.password.v1
version: "3.7"

services:
  cfml:
    volumes:
      - ./app:/app
    environment:
      cfconfig_inspectTemplate: always
    ports:
      - target: 8080
        published: 8080
      - target: 8443
        published: 8443

networks:
  internal:
    driver: bridge

secrets:
  cfml.admin.password:
    external: false
    file: ./.secrets/cfml.admin.password.dev
docker-compose.override.yml
docker-compose.yml

the last slide was a lie 🤥

The real world doesn't look like demos.

┻━┻︵ \(°□°)/ ︵ ┻━┻

Swarm Creation

Swarm Creation

# Create Docker Droplet
doctl compute droplet create test 
    --size 1gb 
    --image docker-18-04 
    --region nyc1

# List Droplets
doctl compute droplet list

#Delete a Droplet
doctl compute droplet delete 123456

#List SSH Key Ids and Names
doctl compute droplet list --format "ID,Name"

(Official DigitalOcean Command-Line Client)

Swarm Creation

DigitalOcean Swarm Creation

Swarm Initialized

Environment Variables

  • Environment for configuration
  • Modular and easy to change
  • Managed in .env file
.env
CI_PROJECT_NAMESPACE=mjclemente
OTHER_SETTING=
FOO=bar

${Env} in Compose

version: "3.7"

services:
  cfml:
    image: "registry.gitlab.com/${CI_PROJECT_NAMESPACE}/starter-swarm-cfml/cfml:${BUILD_TAG:-latest}"
    build:
      context: .
      dockerfile: ./build/cfml/Dockerfile
    environment:
      PORT: 8080
      SSL_PORT: 8443
      cfconfigfile: .CFConfig.json
      cfconfig_inspectTemplate: never
      CF_ADMINPASSWORD: <<SECRET:cfml.admin.password>>
    secrets:
      - source: cfml.admin.password
        target: cfml.admin.password

${Env} in CFConfig.json

{
    "adminPassword":"${CF_ADMINPASSWORD}",
    "applicationListener":"modern",
    "applicationMode":"curr2root",
    "applicationTimeout":"1,0,0,0",
    "cacheDefaultFile":"",
    "cacheDefaultFunction":"",
    "cacheDefaultHTTP":"",
    "cacheDefaultInclude":"",
    "cacheDefaultObject":"object",
    "cacheDefaultQuery":"",
    "cacheDefaultResource":"",
    "cacheDefaultTemplate":"",
    "cacheDefaultWebservice":"",
    "caches":{},
    "CGIReadOnly":"true",
    "clientCookies":"true",
    "clientManagement":"false",
    "clientTimeout":"90,0,0,0",

(Same behavior in server.json)

Env variables prone to leak

Docker Swarm Secrets

  • Immutable by design
  • Account for rotation
  • Account for Dev and Production
  • Team conventions are essential
secrets:
  cfml.admin.password:
    external: true
    name: cfml.admin.password.v1
secrets:
  cfml.admin.password:
    external: false
    file: ./.secrets/cfml.admin.password.dev
docker-compose.override.yml
docker-compose.yml
version: "3.7"

services:
  cfml:
    ...
    environment:
      PORT: 8080
      SSL_PORT: 8443
      cfconfigfile: .CFConfig.json
      cfconfig_inspectTemplate: never
      CF_ADMINPASSWORD: <<SECRET:cfml.admin.password>>
    secrets:
      - source: cfml.admin.password
        target: cfml.admin.password

( ••)
( ••)>⌐■-■
(⌐■_■) #Secrets!

CI/CD with Gitlab Runners

  • Configured via .gitlab-ci.yml
  • Run automagically
  • Multiple types of runners
  • Use a dedicated SSH Key

₍₍ ᕕ( ಠ‿ಠ)ᕗ

.gitlab-ci.yml
  • File containing all definitions of how your project should be built
before_script:
before_script:
## We're gonna log into the gitlab registry, as that's where these images are stored
  - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
## Git needed to get the date from the commit sha
  - apk add git
## So we can see what's going on in the logs
  - docker info
## setup environment variables
  - [configuration continues]

Getting everything set up

.gitlab-ci.yml

Tagging Custom Images

  • Don't just use "latest"
  • Combination of date and commit
before_script:
  - [earlier configuration]
  ## Use git `show` with --format=%ci to get ISO 8601 date
  - export COMMIT_TIME=$(git show -s --format=%ci $CI_COMMIT_SHA)
  ## Use first 10 characters of the datetime (ie: 2019-03-19)
  - export COMMIT_TIME_SHORT=$(echo $COMMIT_TIME | head -c10) 
  - export BUILD_TAG="${COMMIT_TIME_SHORT}_$CI_COMMIT_SHORT_SHA"
.gitlab-ci.yml

Control Pipeline Stages

deploy:
  stage: deploy
  only:
    - deploy
  except:
    variables:
      - $CI_COMMIT_MESSAGE =~ /Initial commit/i
      - $CI_COMMIT_MESSAGE =~ /skip deploy/i
      - $CI_COMMIT_MESSAGE =~ /don't deploy/i
.gitlab-ci.yml

Building Custom Images

build:
  stage: build
  only:
    - deploy
  script:
    ## Build the image, with the build tag and the latest tag
    - docker build --tag $CONTAINER_IMAGE:$BUILD_TAG --tag $CONTAINER_IMAGE:latest -f ./build/cfml/Dockerfile .
    ## List images, so we can confirm success
    - docker image ls
    ## Push with the build tag
    - docker push $CONTAINER_IMAGE:$BUILD_TAG
    ## Push with latest
    - docker push $CONTAINER_IMAGE:latest
.gitlab-ci.yml

Stack Deployment

Stack Deployment

deploy:
  stage: deploy
  script:
    - [a lot of SSH related config]
    ## Enable SSH functionality made possible in 18.0.9 to switch our context to the remote server
    - export DOCKER_HOST=ssh://root@${HOST_IP}
    ## Deploy the stack - registry auth is for gitlab
    - docker stack deploy -c docker-compose.yml -c docker-compose.prod.yml basetest --with-registry-auth
.gitlab-ci.yml
  • Swarm's missing UI
  • Configuration and management
  • Separate from application stack

Portainer

FusionReactor (Cloud)

  • Required in multi-node Swarms
  • Free options
  • Provided via (beta) Lucee Extensions
  • Paid options are superior

Sessions and Caching

SSL

(Zero-config tool to make locally trusted development certificates)
+

All things are difficult before they are easy.

Dr. Thomas Fuller, Gnomologia, 1732

Try This At Home

Building a Personal Docker Swarm

Matthew Clemente