Orchestration
I've got two tickets to the game
It'd be great if I could take you to it this Sunday
--Nickelback
Overview
Orchestration aims to provide a convenient and consistent process for working with Rails and Docker without obscuring underlying components.
At its core Orchestration is simply a Makefile
and a set of docker-compose.yml
files with sensible, general-purpose default settings. Users are encouraged to tailor the generated build-out to suit their application; once the build-out has been generated it belongs to the application.
A typical Rails application can be tested, built, pushed to Docker Hub, and deployed to Docker Swarm with the following commands:
make test build push
make deploy [email protected] env_file=/var/configs/myapp.env
Orchestration has been successfully used to build continuous delivery pipelines for numerous production applications with a wide range or requirements.
Example
The below screenshot demonstrates Orchestration being installed in a brand new Rails application with support for PostgreSQL (via the PG gem) and RabbitMQ (via the Bunny gem):
Getting Started
Docker and Docker Compose must be installed on your system.
Install
Add Orchestration to your Gemfile:
gem 'orchestration', '~> 0.4.8'
Install:
bundle install
Setup
Generate configuration files and select your deployment server:
Generate build-out
rake orchestration:install server=unicorn # (or 'puma' [default], etc.)
To rebuild all build-out at any time, pass force=yes
to the above install command.
You will be prompted to enter values for your Docker organisation and repository name. For example, the organisation and repository for https://hub.docker.com/r/rubyorchestration/sampleapp are rubyorchestration
and sampleapp
respectively. If you are unsure of these values, they can be modified later by editing .orchestration.yml
in the root of your project directory.
Configuration files
Orchestration generates the following files where appropriate. Backups are created if a file is replaced.
config/database.yml
config/mongoid.yml
config/rabbitmq.yml
(see RabbitMQ Configuration for more details)config/unicorn.rb
config/puma.rb
You may need to merge your previous configurations with the generated files. You will only need to do this once.
Test and development dependency containers bind to a randomly-generated [at install time] local port to avoid collisions. You may compare e.g. orchestration/docker-compose.test.yml
with the test
section of the generated config/database.yml
to see how things fit together.
When setup is complete, add the generated build-out to Git:
git add .
git commit -m "Add Orchestration gem"
Usage
All make
commands provided by Orchestration (with the exception of test
and deploy
) recognise the env
parameter. This is equivalent to setting the RAILS_ENV
environment variable.
e.g.:
# Stop all test containers
make stop env=test
The default value for env
is development
.
As with any Makefile
targets can be chained together, e.g.:
# Run tests, build, and push image
make test build push
Containers
All auto-detected services will be added to the relevant docker-compose.<environment>.yml
files at installation time.
Start services
make start
Stop services
make stop
Interface directly with docker-compose
$(make compose env=test) logs -f database
Images
Image tags are generated using the following convention:
# See .orchestration.yml for `organization` and `repository` values.
<organization>/<repository>:<git-commit-hash>
# e.g.
acme/anvil:abcd1234
Build an application image
Note that git archive
is used to generate the build context. Any uncommitted changes will not be included in the image.
make build
See build environment for more details.
Push latest image
You must be logged in to a Docker registry. Use the docker login
command (see Docker documentation for further reference).
make push
Development
An .env
file is created automatically in your project root. This file is not stored in version control. Set all application environment variables in this file.
Launching a development server
To load all variables from .env
and launch a development server, run the following command:
make serve
The application environment will be output on launch for convenience.
To pass extra commands to the Rails server:
# Custom server, custom port
make serve server='webrick -p 3001'
# Default server, custom port, custom bind address
make serve server='-p 3001 -b 192.168.0.1'
Testing
A default test
target is provided in your application's main Makefile
. You are encouraged to modify this target to suit your application's requirements.
To launch all dependency containers, run database migrations, and run tests:
make test
Note that Orchestration will wait for all services to become fully available (i.e. running and providing valid responses) before attempting to run tests. This is specifically intended to facilitate testing in continuous integration environments.
(See sidecar containers if you are running your test/development server inside _Docker)_.
Dependencies will be launched and then tested for readiness. The retry limit and interval time for readiness tests can be controlled by the following environment variables:
ORCHESTRATION_RETRY_LIMIT # default: 10
ORCHESTRATION_RETRY_INTERVAL # default: 5 [seconds]
(Local) Production
Run a production environment locally to simulate your deployment platform:
make start env=production
Deployment to Docker Swarm
To deploy your application to a local Docker Swarm use:
make deploy
Deploy to a remote swarm
To connect via SSH to a remote swarm and deploy, pass the manager
parameter:
make deploy [email protected]
Use a custom stack name
The Docker stack name defaults to the name of your repository (as defined in .orchesration.yml
) and the Rails environment, e.g. anvil_production
.
To override this default, pass the project_name
parameter:
make deploy project_name=acme_anvil_production
This variable will also be available as COMPOSE_PROJECT_NAME
for use within your docker-compose.yml
. e.g. to explicitly name a network after the project name:
networks:
myapp:
name: "${COMPOSE_PROJECT_NAME}"
Use a custom .env
file
Specify a path to a local .env
file (see Docker Compose documentation):
make deploy env_file=/path/to/.env
Note that the following two variables must be set in the relevant .env
file (will look in the current working directory if no path provided):
# Published port for your application service:
CONTAINER_PORT=3000
# Number of replicas of your application service:
REPLICAS=5
It is also recommended to set SECRET_KEY_BASE
, DATABASE_URL
etc. in this file.
Logs
The output from most underlying components is hidden in an effort to make continuous integration pipelines clear and concise. All output is retained in log/orchestration.stdout.log
and log/orchestration.stderr.log
. i.e. to view all output during a build:
tail -f log/orchestration*.log
A convenience Makefile
target dump
is provided which will output all consumed stdout and stderr:
make dump
Build Environment
The following environment variables will be passed as ARG
variables when building images:
BUNDLE_BITBUCKET__ORG
BUNDLE_GITHUB__COM
Set these variables in your shell if your Gemfile
references privately-hosted gems on either Bitbucket or GitHub.
See related documentation:
- https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
- https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html
Healthchecks
Healthchecks are automatically configured for your application. A healthcheck utility is provided in orchestration/healthcheck.rb
. The following environment variables can be configured (in the app
service of orchestration/docker-compose.production.yml
):
Variable | Meaning | Default Value |
---|---|---|
WEB_HOST |
Host to reach application (from inside application container) | localhost |
WEB_PORT |
Port to reach application (from inside application container) | 8080 |
WEB_HEALTHCHECK_PATH |
Path expected to return a successful response | / |
WEB_HEALTHCHECK_READ_TIMEOUT |
Number of seconds to wait for data before failing healthcheck | 10 |
WEB_HEALTHCHECK_OPEN_TIMEOUT |
Number of seconds to wait for connection before failing healthcheck | 10 |
WEB_HEALTHCHECK_SUCCESS_CODES |
Comma-separated list of HTTP status codes that will be deemed a success | 200,202,204 |
If your application does not have a suitable always-available route to use as a healthcheck, the following one-liner may be useful:
# config/routes.rb
get '/healthcheck', to: proc { [200, { 'Content-Type' => 'text/html' }, ['']] }
In this case, WEB_HEALTHCHECK_PATH
must be set to /healthcheck
.
Dockerfile
A Dockerfile
is automatically generated based on detected dependencies, required build steps, Ruby version, etc.
Real-world applications will inevitably need to make changes to this file. As with all Orchestration build-out, the provided Dockerfile
should be treated as a starting point and customisations should be applied as needed.
Entrypoint
An entrypoint script for your application is provided which does the following:
- Runs the
CMD
process as the same system user that launched the container (rather than the defaultroot
user); - Creates various required temporary directories and removes stale
pid
files; - Adds a route
host.docker.internal
to the host machine running the container (mimicking the same route provided by Docker itself on Windows and OS X).
Sidecar Containers
If you need to start dependency services (databases, etc.) and connect to them from a Docker container (an example use case of this would be running tests in Jenkins using its Docker agent) then the container that runs your tests must join the same Docker network as your dependency services.
To do this automatically, pass the sidecar
parameter to the start
or test
targets:
make test sidecar=1
RabbitMQ Configuration
The Bunny RabbitMQ gem does not recognise config/rabbitmq.yml
or RABBITMQ_URL
. If your application uses RabbitMQ then you must manually update your code to reference this file, e.g.:
connection = Bunny.new(config_for(:rabbit_mq)['url'])
connection.start
Orchestration generates the following config/rabbitmq.yml
:
development:
url: amqp://127.0.0.1:51070
test:
url: amqp://127.0.0.1:51068
production:
url: <%= ENV['RABBITMQ_URL'] %>
Using this approach, the environment variable RABBITMQ_URL
can be used to configure Bunny in production (similar to DATABASE_URL
and MONGO_URL
).
This is a convention of the Orchestration gem intended to make RabbitMQ configuration consistent with other services.
License
Contributing
Feel free to make a pull request. Use make test
to ensure that all tests, Rubocop checks, and dependency validations pass correctly.