Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add support for developing the rails app within Docker
Some notes about the rationale behind some of the decisions made in implementing this:
  - Initially got a version of this working with rails in a container with a mounted volume for the app code, but it's really really slow. Appears to be a known problem when using a mounted volume on OSX https://forums.docker.com/t/file-access-in-mounted-volumes-extremely-slow-cpu-bound/8076/276. Added docker-sync configuration and instructions on how to install/use.
  - Building the image will copy the full application code into /project_root within the container instead of /app. This is in order to disambiguate this from the app directory within the root of the project. Most of the docs for docker-sync will reference /app, so just be aware of this
  - Added sync excludes for common unneeded directories that may exist in dev workdirs. If you do begin using this, it would be a good idea to clean out any temp files you have in your directory to speed up build/syncs
  - There are some shenanigans I have to play with the bundled gems and the Gemfile. The rails container will bundle gems into a /bundle directory. This is to preserve any changes bundler will make to the Gemfile.lock, so that we can copy it back to the host once sync is started. I'm running an additional command at the end of the Dockerfile to copy of the lock file back to project_root in the case when someone is not using docker-sync
  - Had to add a USER env to the compose to reproduce the "developer is an admin" behavior that we all expect
  - Added install of fits (and the java runtime) to the rails image so that rake curatend:ci would run correctly
  - The rails_entry script will additionally create the test db for you to simplify running tests in the container. We may want to remove this in the future and separate the test scripts/compose/etc from dev
  - The rails app is not very fault tolerant if, at start time, mysql isn't ready. Initially I added a healthcheck on mariadb and a rails depends_on mysql healthy condition, thinking this should prevent rails from starting until mysql is healthy. Unfortunately, the healthy dependency condition is no longer available in v3 of docker compose. Instead, this required copying the wait-for-it script referenced in peter-evans/docker-compose-healthcheck#3 and adding a rails entry script that will handle all the things associated with first starting the rails app, including waiting for mysql to start. Left the healthy script there since it may still prove useful
  - Broke out the readme into separate readme files now that there is an entirely different set of instructions when using Docker and Docker-sync. README.md will continue to give instruction on how to install/run locally on a host. README_DOCKER.md will contain instructions on how to do these things with Docker
  - Finally, I just want to again note that this only solves the problem of using Docker in development. We need to do things differently if we want to use Docker for CI and other deploys. There are pieces of this that can be reused, but how we separate those out into a common base dockerfile/compose files would need to be discussed

Ideally, adding this would have required making no changes to existing app code, but I did have to make several changes:
  - Running rails app under compose needs to use network names for external services, ie: specs and configs can't assume things are on localhost. Changed solr/fedora/database configs to use env if available, but fallback to localhost if not there to continue supporting development on the host without Docker (such as in the Travis ci atm)
  - Had to add config.allow_http_connections_when_no_cassette to spec_support. The config was already ignoring localhost, which worked when developing locally on a host. In docker this won't work because it will make calls to the "jetty" and "mysql" hosts and will fail because it couldn't find a cassette that matches. Likewise, changed the id_service_spec to just match on path and method for the cassette so that it would successfully match when a spec made the call to jetty.
  - Added a byebug initializer so that we can debug remotely, since this is technically a remote machine
  - Added .docker-sync to gitignore for cases when someone runs the sync as a daemon
  - Now have multiple Dockerfiles, so had to do a little clean up of the file locations. Note: This docker-compose is still for development only. We'll need to break this out later if we ever decide to use this for ci/staging/etc
  • Loading branch information
Justin Gondron committed Jul 27, 2018
1 parent efea632 commit 75f89dd
Show file tree
Hide file tree
Showing 20 changed files with 373 additions and 57 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
@@ -0,0 +1,2 @@
* text=auto
*.sh text eol=lf
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -34,3 +34,4 @@ spec/jetty_generator
.tags
.byebug_history
.DS_Store
.docker-sync
52 changes: 3 additions & 49 deletions README.md
Expand Up @@ -6,8 +6,9 @@ This application will be deployed to [curate.nd.edu](http://curate.nd.edu).

Its primary function is to be a self-deposit interface for our institutional digital repository.

## Installation Notes
Note: If using Docker, see README_DOCKER.md for instructions.

## Installation Notes
Installing the clamav gem on OS X is a trying process. It can be safely excluded
from your development environment

Expand All @@ -18,12 +19,6 @@ bundle install --without headless
## Testing

### 1. Start dependencies
#### With Docker
```console
docker-compose -f docker-compose-test.yml up -d
```

#### Without Docker
```console
bundle exec rake curatend:jetty:start
```
Expand Down Expand Up @@ -53,16 +48,6 @@ Certain curation concern types are never created interactively:
### 1. Start dependencies
Before you start the web-server you'll need to make sure Fedora and SOLR are running.

#### With Docker

You can run MySQL, Fedora and Solr via Docker:

```console
docker-compose up -d
```

#### Without Docker

Start Fedora and SOLR via jetty:

```console
Expand All @@ -77,13 +62,11 @@ mysql.server start

### 2. Initialize the database
Load the database schema into MySQL:

```console
bundle exec rake db:schema:load
```

To seed database with test data (skip this step when using Docker):

To seed database with test data:
```console
bundle exec db:seed:dev
```
Expand All @@ -102,35 +85,6 @@ If you don't need SSL, use the following command:
bundle exec rails server
```

## Rebuilding curate-jetty Docker image

To rebuild the Docker image for running jetty, use the following command:

```console
docker build . -t ndlib/curate-jetty
```

To push your image to Dockerhub:

```console
docker login
docker push ndlib/curate-jetty
```

To rebuild the image with pre-generated seed data:
```console
# First reset to the base image
docker stop curate-jetty && docker rm curate-jetty
docker run -p 8983:8983 -d --name curate-jetty -t ndlib/curate-jetty
# Run seed scripts from the project root directory
bundle exec rake db:schema:load db:seed:dev
# Commit these changes to your image
docker commit curate-jetty ndlib/curate-jetty-devseed
# Push it up as the dev seed image
docker login
docker push ndlib/curate-jetty-devseed
```

## Release Documentation

See the [release documentation](https://docs.google.com/a/nd.edu/document/d/16weRctSzt8Iw2y55nwOKPBSgGDO_4lgti3CxaW3P2pc/edit?usp=sharing) for building a release.
55 changes: 55 additions & 0 deletions README_DOCKER.md
@@ -0,0 +1,55 @@
# Developing CurateND with Docker

## Installation Notes
Install Docker: https://www.docker.com/

Install docker-sync: http://docker-sync.io/

Start the stack:
```console
docker-sync-stack start
```

## Testing
To execute the full test suite, run
```console
docker-compose exec rails bundle exec rake
```
To run individual specs, run
```console
docker-compose exec rails bundle exec rspec spec/path/to/file_spec.rb
```

## Debugging the application
```console
docker-compose exec rails bundle exec byebug --remote localhost:9876
```

## Rebuilding curate-jetty Docker image

To rebuild the Docker image for running jetty, use the following command:

```console
docker build . -t ndlib/curate-jetty
```

To push your image to Dockerhub:

```console
docker login
docker push ndlib/curate-jetty
```

To rebuild the image with pre-generated seed data:
```console
# First reset to the base image
docker stop curate-jetty && docker rm curate-jetty
docker run -p 8983:8983 -d --name curate-jetty -t ndlib/curate-jetty
# Run seed scripts from the project root directory
bundle exec rake db:schema:load db:seed:dev
# Commit these changes to your image
docker commit curate-jetty ndlib/curate-jetty-devseed
# Push it up as the dev seed image
docker login
docker push ndlib/curate-jetty-devseed
```
4 changes: 2 additions & 2 deletions config/database.yml
Expand Up @@ -11,14 +11,15 @@
].detect { |f| f && File.exist?(f) }
port = ENV["BOXEN_MYSQL_PORT"] || "3306"
host = ENV["MYSQL_HOST"] || "127.0.0.1"
%>

mysql_connection: &mysql_connection
<% if socket %>
host: localhost
socket: <%= socket %>
<% else %>
host: 127.0.0.1
host: <%= host %>
port: <%= port %>
<% end %>

Expand All @@ -37,7 +38,6 @@ local_user: &local_user
development: &development
<<: *local_user
database: curate_nd_development
host: 127.0.0.1
timeout: 5000

development_remote_purl_database:
Expand Down
9 changes: 7 additions & 2 deletions config/fedora.yml
Expand Up @@ -2,14 +2,19 @@
# FYI: This file is replaced by a copy for deploys
# to ci, pre_production, and production.
#
<%
port = ENV["FEDORA_PORT"] || "8983"
host = ENV["FEDORA_HOST"] || "127.0.0.1"
%>

development:
user: fedoraAdmin
password: fedoraAdmin
url: http://127.0.0.1:8983/fedora
url: <%= "http://#{host}:#{port}/fedora" %>
test: &TEST
user: fedoraAdmin
password: fedoraAdmin
url: <%= "http://127.0.0.1:#{ENV['TEST_JETTY_PORT'] || 8983}/fedora-test" %>
url: <%= "http://#{host}:#{port}/fedora-test" %>
ci:
<<: *TEST
production:
Expand Down
7 changes: 7 additions & 0 deletions config/initializers/byebug.rb
@@ -0,0 +1,7 @@
if Rails.env.development?
require 'byebug/core'
#Byebug.wait_connection = true
port = ENV.fetch("BYEBUG_SERVER_PORT", 9876).to_i
print "Starting byebug server for remote debugging on port #{port}\n"
Byebug.start_server 'localhost', port
end
9 changes: 7 additions & 2 deletions config/solr.yml
Expand Up @@ -5,10 +5,15 @@

# This is a sample config file that does not have multiple solr instances. You will also need to be sure to
# edit the fedora.yml file to match the solr URL for active-fedora.
<%
port = ENV["SOLR_PORT"] || "8983"
host = ENV["SOLR_HOST"] || "127.0.0.1"
%>

development:
url: http://localhost:8983/solr/development
url: <%= "http://#{host}:#{port}/solr/development" %>
test: &TEST
url: <%= "http://127.0.0.1:#{ENV['TEST_JETTY_PORT'] || 8983}/solr/test" %>
url: <%= "http://#{host}:#{port}/solr/test" %>
cucumber:
<<: *TEST
ci:
Expand Down
35 changes: 34 additions & 1 deletion docker-compose.yml
@@ -1,15 +1,48 @@
version: '3'
services:
mysql:
image: mariadb
build:
context: ./docker/
dockerfile: Dockerfile.mariadb
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: curate_nd_development
volumes:
- ./docker/mysql-utf8.cnf:/etc/mysql/conf.d/mysql-utf8.cnf
healthcheck:
interval: 30s
timeout: 10s
retries: 5
jetty:
image: ndlib/curate-jetty-devseed
ports:
- "8983:8983"
rails:
build:
context: .
dockerfile: docker/Dockerfile.rails
command: bash docker/rails_entry.sh
environment:
BUNDLE_PATH: "/bundle"
FEDORA_HOST: jetty
FEDORA_PORT: 8983
MYSQL_HOST: mysql
SOLR_HOST: jetty
SOLR_PORT: 8983
# Need to pass the user running docker into the container so that
# config/admin_usernames.yml pulls in the user as an admin. This is
# to replicate existing behavior on OSX and may not work correctly on
# another OS
USER: #{USER}
ports:
- "3000:3000"
volumes:
- curate_nd_rails_sync:/project_root:nocopy
depends_on:
- mysql
- jetty
volumes:
curate_nd_rails_sync:
external: true
8 changes: 8 additions & 0 deletions docker-sync.yml
@@ -0,0 +1,8 @@
version: "2"

options:
verbose: true
syncs:
curate_nd_rails_sync:
src: '.'
sync_excludes: ['.git', 'tmp', 'jetty', 'vendor/bundle', 'log']
File renamed without changes.
5 changes: 5 additions & 0 deletions docker/Dockerfile.mariadb
@@ -0,0 +1,5 @@
FROM mariadb

COPY mysql-healthcheck /usr/local/bin/

HEALTHCHECK CMD ["mysql-healthcheck"]
26 changes: 26 additions & 0 deletions docker/Dockerfile.rails
@@ -0,0 +1,26 @@
FROM ruby:2.1.10

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs unzip

# Install Fits (which requires java)
RUN apt-get install -y default-jre
RUN mkdir /fits
WORKDIR /fits
RUN wget https://projects.iq.harvard.edu/files/fits/files/fits-0.6.2.zip
RUN unzip fits-0.6.2.zip
RUN chmod 755 /fits/fits-0.6.2/fits.sh
ENV PATH="/fits/fits-0.6.2:${PATH}"

# Put the installed gems outside of project_root so that the sync volume won't interfere
RUN mkdir /bundle
COPY Gemfile /bundle
COPY Gemfile.lock /bundle
WORKDIR /bundle
RUN bundle install --without headless --path /bundle

RUN mkdir /project_root
WORKDIR /project_root
COPY . /project_root

# Gemfile.lock may have changed after bundling, copy it back into the project_root
RUN cp /bundle/Gemfile.lock /project_root/
1 change: 1 addition & 0 deletions docker/byebug.sh
@@ -0,0 +1 @@
docker-compose exec rails bundle exec byebug --remote localhost:9876
25 changes: 25 additions & 0 deletions docker/mysql-healthcheck
@@ -0,0 +1,25 @@
#!/bin/bash
set -eo pipefail

if [ "$MYSQL_RANDOM_ROOT_PASSWORD" ] && [ -z "$MYSQL_USER" ] && [ -z "$MYSQL_PASSWORD" ]; then
# there's no way we can guess what the random MySQL password was
echo >&2 'healthcheck error: cannot determine random root password (and MYSQL_USER and MYSQL_PASSWORD were not set)'
exit 0
fi

host="$(hostname --ip-address || echo '127.0.0.1')"
user="${MYSQL_USER:-root}"
export MYSQL_PWD="${MYSQL_PASSWORD:-$MYSQL_ROOT_PASSWORD}"

args=(
# force mysql to not use the local "mysqld.sock" (test "external" connectibility)
-h"$host"
-u"$user"
--silent
)

if select="$(echo 'SELECT 1' | mysql "${args[@]}")" && [ "$select" = '1' ]; then
exit 0
fi

exit 1
10 changes: 10 additions & 0 deletions docker/rails_entry.sh
@@ -0,0 +1,10 @@
# All the things that will execute when starting the rails service
# Copy the generated lock file back into the project root. This is to sync this change back to the
# application after the container has mounted the sync volume
cp /bundle/Gemfile.lock ./
bash docker/wait-for-it.sh mysql:3306
bundle exec rake db:schema:load
# If we find people are ok with just starting a shell in the container and running the db create/load before running specs, then we can remove this
RAILS_ENV=test bundle exec rake db:create db:schema:load

exec bundle exec thin start -p 3000 --ssl --ssl-key-file dev_server_keys/server.key --ssl-cert-file dev_server_keys/server.crt

0 comments on commit 75f89dd

Please sign in to comment.