capistrano-docker_cluster 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGE_LOG.md +7 -0
- data/MIT_LICENSE.txt +21 -0
- data/README.md +155 -0
- data/bin/docker-cluster +423 -0
- data/capistrano-docker_cluster.gemspec +39 -0
- data/lib/capistrano/docker_cluster.rb +10 -0
- data/lib/capistrano/docker_cluster/scripts.rb +207 -0
- data/lib/capistrano/docker_cluster/version.rb +7 -0
- data/lib/capistrano/tasks/docker_cluster.rake +144 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b21dd5be728c678022ef6ae1b2bc6034e8610cac1c2152fd3cde26e852e0bc35
|
4
|
+
data.tar.gz: 4a297e06578a06670daab133c6dca12c19d3b2596a82d9f26605d1391adade49
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5fc892bf4d56e78fabec0603b8e03ae1edbf151d54753fc46abc71d2a5d0272fd46b5612da053c2e6925a7d7e560fa99d024aea4f26bc57c8456236cd38728c1
|
7
|
+
data.tar.gz: 5f903a514ea9366f8f4d502b0b32c2cbe821be92edd8344e9d74519cd19346f09c8a348de8723045bd429b1e5c2106ccfe2b3507cf4f5b5cd3d5c2787d78ff58
|
data/CHANGE_LOG.md
ADDED
data/MIT_LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Brian Durand
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Capistrano Docker Cluster
|
2
|
+
|
3
|
+
This gem provides a recipe to use capistrano to deploy docker applications.
|
4
|
+
|
5
|
+
This allows you to deploy an application across a cluster of servers running docker. There are, of course, other methods of doing this (kubernetes, docker-compose, etc.). This method can fill a nitch of allowing you to dockerize your application but keep the deployment simple and use existing configs if you're already deploying via capistrano.
|
6
|
+
|
7
|
+
You application will be deployed by pulling a tag from a docker repository on the remote servers and then starting it up as a cluster using the `bin/docker_cluster` script. This script will start a cluster of docker containers by spinning up a specified number of containers from the same image and configuration. It does this gracefully by first shutting down any excess containers and then restarting the containers one at a time. The script can perform an optional health check to determine if a container is fully running before shutting down the next contaner.
|
8
|
+
|
9
|
+
If you specify a port mapping for the containers, the container ports will be mapped to incrementing host ports so you can run multiple server containers fronted by a load balancer.
|
10
|
+
|
11
|
+
The full set of arguments can be found in the `bin/docker-cluster` script.
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
|
15
|
+
The deployment is configured with the following properties in your capistrano recipe.
|
16
|
+
|
17
|
+
* `docker_repository` - The URI for the repository where to pull images from. If you are building images on the docker host (i.e. for a staging server), this can just be the local respoitory.
|
18
|
+
|
19
|
+
* `docker_tag` - The tag of the image to pull for starting the containers.
|
20
|
+
|
21
|
+
* `docker_roles` - List of server roles that will run docker containers. This defaults to `:docker`, but you can change it to whatever server roles you have in your recipe.
|
22
|
+
|
23
|
+
* `docker_user` - User to use when running docker commands on the remote host. This user must have access to the docker daemon. Default to the default capistrano user.
|
24
|
+
|
25
|
+
* `docker_env` - Environment variables needed to run docker commands. You may need to set `HOME` if you are pulling docker images from a remote repository using a use that is not the default deploy user.
|
26
|
+
|
27
|
+
* `docker_apps` - List of apps to deploy. Each app is deployed to its own containers with its own configuration. This value should usually be defined on a server role.
|
28
|
+
|
29
|
+
* `docker_prefix` - Optional prefix to attach to docker container names. This can be used to distinguish containers where multiple applications are running on the same host with the same `docker_apps` names (for instance, if you run staging containers on the same hardware as your production containers).
|
30
|
+
|
31
|
+
* `docker_configs` - List of global configuration files for starting all containers.
|
32
|
+
|
33
|
+
* `docker_app_configs` - Map of configuration files for starting specific docker apps.
|
34
|
+
|
35
|
+
* `docker_args` - List of global command line arguments for all continers.
|
36
|
+
|
37
|
+
* `docker_app_args` - Map of command line arguments for starting specific docker apps.
|
38
|
+
|
39
|
+
### Example Configuration
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
set :docker_repository, repository.example.com/myapp
|
43
|
+
|
44
|
+
set :docker_tag, ENV.fetch("tag")
|
45
|
+
|
46
|
+
# Define two apps; the web app will run on both server01 and server02
|
47
|
+
role :web, [server01, server02], user: 'app', docker_apps: [:web]
|
48
|
+
role :async, [server01], user: 'app', docker_apps: [:async]
|
49
|
+
|
50
|
+
# These configuration files will apply to all containers
|
51
|
+
set :docker_configs, ["config/volumes.properties"]
|
52
|
+
|
53
|
+
# Unlike config files, args can be dynamically generated at runtime
|
54
|
+
set :docker_args, ["--env=ASSET_HOST=#{ENV.fetch('asset_host')}"]
|
55
|
+
|
56
|
+
# These configuration files and args will apply only to each app.
|
57
|
+
set :docker_app_configs, {
|
58
|
+
web: ["config/web.properties"],
|
59
|
+
async: ["config/async.properties"]
|
60
|
+
}
|
61
|
+
|
62
|
+
set :docker_app_args, {
|
63
|
+
web: ["--env=SERVER_HOST=#{fetch(:server_host)}"]
|
64
|
+
}
|
65
|
+
|
66
|
+
# If your capistrano user doesn't have access to the docker daemon, you can specify a different user.
|
67
|
+
set :docker_user, "root"
|
68
|
+
|
69
|
+
# You can also specify environment variables that may be needed for running docker commands.
|
70
|
+
set :docker_env, {"HOME" => "/root"}
|
71
|
+
```
|
72
|
+
|
73
|
+
## Server Scripts
|
74
|
+
|
75
|
+
Scripts to control your docker applications are put into the `bin` directory in the capistrano target directory. These scripts are wrappers around the `bin/docker-cluster` script and supply the configuration values to that script for each app you've defined.
|
76
|
+
|
77
|
+
### bin/start
|
78
|
+
|
79
|
+
```bash
|
80
|
+
bin/start app [additional arguments]
|
81
|
+
```
|
82
|
+
|
83
|
+
* The start script will start up the docker containers for you app.
|
84
|
+
* If the containers are not running, they will be started.
|
85
|
+
* If excess containers are running, they will be stopped.
|
86
|
+
* If the containers are running, but they are running on a different image version, they will be replaced one at a time by new containers.
|
87
|
+
|
88
|
+
|
89
|
+
### bin/stop
|
90
|
+
|
91
|
+
```bash
|
92
|
+
bin/stop app
|
93
|
+
```
|
94
|
+
|
95
|
+
* All the containers associated with the app will be stopped.
|
96
|
+
|
97
|
+
### bin/run
|
98
|
+
|
99
|
+
```bash
|
100
|
+
bin/run app [additional arguments]
|
101
|
+
```
|
102
|
+
|
103
|
+
* Start a one off container with the specified app config.
|
104
|
+
* The normal port mapping used for the cluster will not be included; if you want to expose ports, you'll need to supply the port mapping.
|
105
|
+
* All apps defined in `docker_apps` as well as `docker_app_configs` and `docker_app_args` will be included as apps. This allows you to set up things like a "console" app which will open a console on a container for debugging, etc. without having it be part of the cluster apps.
|
106
|
+
|
107
|
+
## Capfile and SCM setting
|
108
|
+
|
109
|
+
The deployment does not require a source control management system to perform the build. To turn off this feature you need to include this in your project's Capfile to include the Docker deployment recipe and turn off the default (git) SCM setting for capistrano.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
require 'capistrano/docker_cluster'
|
113
|
+
install_plugin Capistrano::Scm::None::Plugin
|
114
|
+
```
|
115
|
+
|
116
|
+
## Remote Repository
|
117
|
+
|
118
|
+
If your `docker_repository` points to a remote repository, then the tag specified by `docker_tag` will be pulled from that repository during the deploy. If the repository requires authentication, then you should implement the `docker:authenticate` task to authenticate all servers in the `docker_role` role with the repository. You can use the `as_docker_user` method to run docker commands as the user specified in `docker_user`.
|
119
|
+
|
120
|
+
### Example Authentication with Amazon ECR
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
namespace :docker do
|
124
|
+
task :authenticate do
|
125
|
+
bin_dir = "#{fetch(:release_path)}/bin"
|
126
|
+
ecr_login_path = "#{bin_dir}/ecr_login"
|
127
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
128
|
+
execute(:mkdir, "-p", bin_dir)
|
129
|
+
upload! StringIO.new(ecr_login_script), ecr_login_path
|
130
|
+
as_docker_user do
|
131
|
+
execute :bash, ecr_login_path
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Script to run the aws ecr get-login results, but with passing the password in
|
138
|
+
# via STDIN so that it doesn't appear in the capistrano logs.
|
139
|
+
def ecr_login_script
|
140
|
+
<<~BASH
|
141
|
+
#!/usr/bin/env bash
|
142
|
+
|
143
|
+
set -o errexit
|
144
|
+
|
145
|
+
read -sra cmd < <(/usr/bin/env aws ecr get-login --no-include-email)
|
146
|
+
pass="${cmd[5]}"
|
147
|
+
unset cmd[4] cmd[5]
|
148
|
+
/usr/bin/env "${cmd[@]}" --password-stdin <<< "$pass"
|
149
|
+
BASH
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
## Building Docker Image
|
154
|
+
|
155
|
+
If you need to build the docker image on the remote host as part of the deploy (for example if you're deploying pre-release code to a staging server), you can implement the `docker:build` task to build your docker image. You must also tag the image with the value in the `:docker_tag` property.
|
data/bin/docker-cluster
ADDED
@@ -0,0 +1,423 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# Script to manage a cluster of docker containers with the same configuration running on sequential ports.
|
4
|
+
#
|
5
|
+
# The --name parameter will be used to assign names to the containers as "name.number" where number is the
|
6
|
+
# number of the started container starting at 1.
|
7
|
+
#
|
8
|
+
# The --count paramter can be used to specify the number of containers to start up. If the script finds that
|
9
|
+
# more than that number of containers are already running, it will first shut down the excess containers.
|
10
|
+
# It will then shutdown the running containers one at a time and start up a new one before shutting down the
|
11
|
+
# next container.
|
12
|
+
#
|
13
|
+
# The --image parameter specifies the docker image to start up. It can be either a tag or an image id. This
|
14
|
+
# parameter is required if --count is greater than zero.
|
15
|
+
#
|
16
|
+
# The --port paramters specifies port mapping in the form "container_port:base_host_port" where container
|
17
|
+
# port is the exposed port to map from the containers. The base host port is the port number to start mapping
|
18
|
+
# the container ports to. If the base host port is not specified, then the container port will be used as the
|
19
|
+
# base host port. For instance, `--port=80:8000 --count=2` will start two containers with the first one mapping
|
20
|
+
# host port 8000 to container port 80 and the second mapping host port 8001 to container port 80.
|
21
|
+
#
|
22
|
+
# The --hostname parameter can be used to specify a base host name for the containers. The host name for each
|
23
|
+
# container will be "container_name.base_host_hamer" where container name uses a hyphen instead of a period as
|
24
|
+
# the delimiter.
|
25
|
+
#
|
26
|
+
# The --healthcheck parameter can be used to specify either a command to run inside the container or a URL
|
27
|
+
# to ping from within the container to determine if the container is up. The next container will not be shutdown
|
28
|
+
# until the previous one is determined to be up when this parameter is specified.
|
29
|
+
#
|
30
|
+
# The --timeout parameter can be used to specify a timeout for how long to wait for a container to stop or start
|
31
|
+
# before assuming something is wrong. In the case of stopping the container, the container will be force killed
|
32
|
+
# after the specified number of seconds. If starting a container times out, then the restart script will be stopped
|
33
|
+
# at that point and no more containers will be restarted.
|
34
|
+
#
|
35
|
+
# The --config parameter can be used to specify a file that contains more command line arguments for this script. This
|
36
|
+
# parameter can be specified multiple times. The files should be property files in the form "arg=val" or "arg val" or "arg"
|
37
|
+
# on each line. Comments can be used in the configuration files using "#".
|
38
|
+
#
|
39
|
+
# The --command parameter can be used to specify the command each docker container should run. If multiple command parameters
|
40
|
+
# are specified, they will be concatenated together. You can use this to specify a command and arguments separately.
|
41
|
+
#
|
42
|
+
# The --force parameter can be used to specify that containers should always be restarted. The default behavior
|
43
|
+
# is to only restart containers if they are not running the specified image.
|
44
|
+
#
|
45
|
+
# The --one-off parameter can be used to specify that a one off container should be spun up instead of stopping
|
46
|
+
# and starting containers in the cluster. Any --port, --hostname, --healthcheck, and --name arguments that appear
|
47
|
+
# before this argument will be ignored. If these arguments appear after the --one-off argument, then they will
|
48
|
+
# be passed through to the `docker run` command.
|
49
|
+
#
|
50
|
+
# The --verbose parameter can be used for debugging and will show all the shell commands as they are run.
|
51
|
+
#
|
52
|
+
# All other parameters are passed through to the `docker run` command.
|
53
|
+
|
54
|
+
set -o errexit
|
55
|
+
|
56
|
+
usage() {
|
57
|
+
script_name=$(basename $0)
|
58
|
+
echo "Usage: $script_name"
|
59
|
+
echo " --name CONTAINER_NAME_PREFIX (or --one-off)"
|
60
|
+
echo " [--count CONTAINER_COUNT] (default 1)"
|
61
|
+
echo " [--image DOCKER_IMAGE] (image tag or id; required if --count > 0)"
|
62
|
+
echo " [--port CONTAINER_PORT[:BASE_HOST_PORT]]"
|
63
|
+
echo " [--hostname CONTAINER_BASE_HOST_NAME]"
|
64
|
+
echo " [--healthcheck COMMAND|CURL_URL]"
|
65
|
+
echo " [--timeout SECONDS] (default 120)"
|
66
|
+
echo " [--config CONFIG_FILE_PATH]"
|
67
|
+
echo " [--command DOCKER_RUN_COMMAND]"
|
68
|
+
echo " [--verbose]"
|
69
|
+
echo " All other options are passed through to 'docker run'"
|
70
|
+
}
|
71
|
+
|
72
|
+
read_arguments() {
|
73
|
+
while [ "$1" != "" ]; do
|
74
|
+
typeset arg=$1
|
75
|
+
typeset val=
|
76
|
+
if [[ $arg =~ ^--?[^-]+ ]]; then
|
77
|
+
if [[ $arg =~ ^-.+= ]]; then
|
78
|
+
arg="${1%=*}"
|
79
|
+
val="${1#*=}"
|
80
|
+
fi
|
81
|
+
fi
|
82
|
+
|
83
|
+
case $arg in
|
84
|
+
--image )
|
85
|
+
[[ -z $val ]] && shift && val=$1
|
86
|
+
DOCKER_IMAGE=$val
|
87
|
+
;;
|
88
|
+
--name )
|
89
|
+
[[ -z $val ]] && shift && val=$1
|
90
|
+
CONTAINER_NAME_PREFIX=$val
|
91
|
+
;;
|
92
|
+
--count )
|
93
|
+
[[ -z $val ]] && shift && val=$1
|
94
|
+
CONTAINER_COUNT=$val
|
95
|
+
;;
|
96
|
+
--port )
|
97
|
+
[[ -z $val ]] && shift && val=$1
|
98
|
+
PORT_MAPPING+=($val)
|
99
|
+
;;
|
100
|
+
--hostname )
|
101
|
+
[[ -z $val ]] && shift && val=$1
|
102
|
+
CONTAINER_BASE_HOST_NAME=$val
|
103
|
+
;;
|
104
|
+
--config )
|
105
|
+
[[ -z $val ]] && shift && val=$1
|
106
|
+
cat "$val" > /dev/null
|
107
|
+
typeset config_args=
|
108
|
+
IFS=$'\n\r' config_args=($(sed 's/#.*//g' "$val" | grep -v '^[[:space:]]*$' | sed 's/^ *//g' | sed -E 's/^([^-])/--\1/g' | sed -E 's/^([^ =]+) /\1=/g'))
|
109
|
+
read_arguments ${config_args[@]}
|
110
|
+
unset IFS
|
111
|
+
;;
|
112
|
+
--command )
|
113
|
+
[[ -z $val ]] && shift && val=$1
|
114
|
+
DOCKER_RUN_COMMAND="$DOCKER_RUN_COMMAND $val"
|
115
|
+
;;
|
116
|
+
--one-off )
|
117
|
+
ONE_OFF_CONTAINER="1"
|
118
|
+
CONTAINER_NAME_PREFIX=""
|
119
|
+
CONTAINER_BASE_HOST_NAME=""
|
120
|
+
HEALTHCHECK=""
|
121
|
+
PORT_MAPPING=()
|
122
|
+
;;
|
123
|
+
--healthcheck )
|
124
|
+
[[ -z $val ]] && shift && val=$1
|
125
|
+
HEALTHCHECK=$val
|
126
|
+
;;
|
127
|
+
--timeout )
|
128
|
+
[[ -z $val ]] && shift && val=$1
|
129
|
+
TIMEOUT=$val
|
130
|
+
;;
|
131
|
+
--force )
|
132
|
+
FORCE_RESTART="1"
|
133
|
+
;;
|
134
|
+
--help )
|
135
|
+
usage
|
136
|
+
exit
|
137
|
+
;;
|
138
|
+
--verbose )
|
139
|
+
set -o xtrace
|
140
|
+
CMD_OUT=/dev/stdout
|
141
|
+
;;
|
142
|
+
* )
|
143
|
+
DOCKER_RUN_ARGS="$DOCKER_RUN_ARGS $1"
|
144
|
+
esac
|
145
|
+
shift
|
146
|
+
done
|
147
|
+
}
|
148
|
+
|
149
|
+
# Return port mapping arguments for docker run. Ports are passed in to
|
150
|
+
# the command as `--port base_host_port:container_port`. The host port
|
151
|
+
# will be incremented for each container started so each container will
|
152
|
+
# be mapped to it's own host port.
|
153
|
+
docker_port_args() {
|
154
|
+
typeset port_args=
|
155
|
+
for port_info in "${PORT_MAPPING[@]}"; do
|
156
|
+
typeset split_port=
|
157
|
+
IFS=':' read -ra split_port <<< "$port_info"
|
158
|
+
unset IFS
|
159
|
+
typeset base_port=${split_port[0]}
|
160
|
+
typeset container_port=${split_port[1]}
|
161
|
+
if [[ $container_port == "" ]]; then
|
162
|
+
container_port=$base_port
|
163
|
+
fi
|
164
|
+
typeset host_port=`expr $base_port + $1 - 1`
|
165
|
+
port_args="-p $host_port:$container_port"
|
166
|
+
done
|
167
|
+
echo $port_args
|
168
|
+
}
|
169
|
+
|
170
|
+
# Run the docker health check command. Returns the exit status of
|
171
|
+
# running the command on the specified container. If the command is a
|
172
|
+
# URL, then it will be fetched with curl within the container.
|
173
|
+
docker_healthcheck() {
|
174
|
+
typeset container_id=$1
|
175
|
+
typeset cmd=$2
|
176
|
+
if [[ $cmd =~ ^https?:// ]]; then
|
177
|
+
cmd="curl --silent --fail $cmd"
|
178
|
+
fi
|
179
|
+
echo `/usr/bin/env docker exec "$container_id" $cmd > /dev/null; echo $?`
|
180
|
+
}
|
181
|
+
|
182
|
+
# Stop and remove the container specified by the passed in id.
|
183
|
+
docker_stop() {
|
184
|
+
typeset container_id=$1
|
185
|
+
echo "> sending stop to container $container_id"
|
186
|
+
/usr/bin/env docker stop $container_id > $CMD_OUT || true
|
187
|
+
|
188
|
+
end_time=(expr `date +%s` + $TIMEOUT)
|
189
|
+
while true; do
|
190
|
+
typeset running_info=$(/usr/bin/env docker ps --no-trunc --filter status=running --format "{{.ID}}" | grep -F "$container_id" | cat)
|
191
|
+
if [[ $running_info == "" ]]; then
|
192
|
+
break
|
193
|
+
fi
|
194
|
+
|
195
|
+
if [[ `date +%s` > $end_time ]]; then
|
196
|
+
exit 1
|
197
|
+
else
|
198
|
+
sleep 1
|
199
|
+
fi
|
200
|
+
done
|
201
|
+
|
202
|
+
# If the container is still running and we didn't send a KILL signal, try that now.
|
203
|
+
typeset running_info=$(/usr/bin/env docker ps --no-trunc --filter status=running --format "{{.ID}}" | grep -F "$container_id" | cat)
|
204
|
+
if [[ $running_info != "" && $STOP_SIGNAL != "SIGKILL" ]]; then
|
205
|
+
echo "> container still running; sending kill to container $container_id"
|
206
|
+
/usr/bin/env docker kill --signal=SIGKILL $container_id > $CMD_OUT
|
207
|
+
sleep 1
|
208
|
+
fi
|
209
|
+
|
210
|
+
/usr/bin/env docker container rm $container_id > $CMD_OUT
|
211
|
+
}
|
212
|
+
|
213
|
+
# Remove the specified container name from docker.
|
214
|
+
docker_remove_container_name() {
|
215
|
+
typeset container_name=$1
|
216
|
+
typeset container_info=$(/usr/bin/env docker container ls --no-trunc --format "{{.ID}};{{.Names}};" | grep -F ";$container_name;" | cat)
|
217
|
+
if [[ $container_info != "" ]]; then
|
218
|
+
typeset info_arr=
|
219
|
+
IFS=';' read -ra info_arr <<< "$container_info"
|
220
|
+
unset IFS
|
221
|
+
typeset container_id=${info_arr[1]}
|
222
|
+
/usr/bin/env docker container rm $container_id > $CMD_OUT
|
223
|
+
fi
|
224
|
+
}
|
225
|
+
|
226
|
+
# Shutdown containers that are no longer used. Detected by comparing
|
227
|
+
# the container numbers in the container name to the number of containers
|
228
|
+
# that were requested to start.
|
229
|
+
shutdown_excess_containers() {
|
230
|
+
typeset container_names=()
|
231
|
+
for i in $(seq 1 $CONTAINER_COUNT); do
|
232
|
+
container_names+=("$CONTAINER_NAME_PREFIX.$i")
|
233
|
+
done
|
234
|
+
|
235
|
+
typeset container_info=()
|
236
|
+
typeset line=
|
237
|
+
while IFS= read -r line; do
|
238
|
+
container_info+=( "$line" )
|
239
|
+
done < <( /usr/bin/env docker ps --no-trunc --format ";{{.ID}};{{.Names}};" | grep -F ";$CONTAINER_NAME_PREFIX." | cat )
|
240
|
+
unset IFS
|
241
|
+
|
242
|
+
for info in "${container_info[@]}"; do
|
243
|
+
typeset info_arr=
|
244
|
+
IFS=';' read -ra info_arr <<< "$info"
|
245
|
+
unset IFS
|
246
|
+
typeset container_id=${info_arr[1]}
|
247
|
+
typeset container_name=${info_arr[2]}
|
248
|
+
if [[ ! " ${container_names[@]} " =~ " ${container_name} " ]]; then
|
249
|
+
docker_stop $container_id
|
250
|
+
fi
|
251
|
+
done
|
252
|
+
}
|
253
|
+
|
254
|
+
# Get the image id a container is running.
|
255
|
+
container_image_id() {
|
256
|
+
typeset name="$CONTAINER_NAME_PREFIX.$1"
|
257
|
+
typeset running_info=$(/usr/bin/env docker ps --no-trunc --filter status=running --format ";{{.Image}};{{.Names}};" | grep -F ";$name;" | cat)
|
258
|
+
if [[ $running_info != "" ]]; then
|
259
|
+
typeset info_arr=
|
260
|
+
IFS=';' read -ra info_arr <<< "$running_info"
|
261
|
+
unset IFS
|
262
|
+
echo ${info_arr[1]}
|
263
|
+
fi
|
264
|
+
}
|
265
|
+
|
266
|
+
# Stop the container with the specified name if it is running.
|
267
|
+
stop_container() {
|
268
|
+
typeset name="$CONTAINER_NAME_PREFIX.$1"
|
269
|
+
typeset running_info=$(/usr/bin/env docker ps --no-trunc --format ";{{.ID}};{{.Names}};" | grep -F ";$name;" | cat)
|
270
|
+
if [[ $running_info != "" ]]; then
|
271
|
+
typeset info_arr=
|
272
|
+
IFS=';' read -ra info_arr <<< "$running_info"
|
273
|
+
unset IFS
|
274
|
+
typeset container_id=${info_arr[1]}
|
275
|
+
docker_stop $container_id
|
276
|
+
else
|
277
|
+
docker_remove_container_name "$name"
|
278
|
+
fi
|
279
|
+
}
|
280
|
+
|
281
|
+
start_container() {
|
282
|
+
typeset name="$CONTAINER_NAME_PREFIX.$1"
|
283
|
+
echo "> start container $name"
|
284
|
+
typeset port_args=$(docker_port_args $1)
|
285
|
+
typeset host_arg="${CONTAINER_NAME_PREFIX}-${1}.${CONTAINER_BASE_HOST_NAME}"
|
286
|
+
typeset docker_cmd="/usr/bin/env docker run --detach --name $name --hostname $host_arg $port_args $DOCKER_RUN_ARGS $DOCKER_IMAGE $DOCKER_RUN_COMMAND"
|
287
|
+
typeset container_id=$($docker_cmd)
|
288
|
+
if [[ $container_id == "" ]]; then
|
289
|
+
>&2 echo "container $name failed to start"
|
290
|
+
>&2 echo " command: $docker_cmd"
|
291
|
+
exit 1
|
292
|
+
else
|
293
|
+
echo "> starting container $name: $container_id"
|
294
|
+
fi
|
295
|
+
|
296
|
+
typeset success=1
|
297
|
+
typeset end_time=(expr `date +%s` + $TIMEOUT)
|
298
|
+
while true; do
|
299
|
+
typeset running_info=$(/usr/bin/env docker ps --no-trunc --filter status=running --format ";{{.ID}};{{.Names}};{{.Status}}" | grep -F ";$name;" | cat)
|
300
|
+
if [[ $running_info == "" ]]; then
|
301
|
+
>&2 echo "container $name not running when it was expected"
|
302
|
+
exit 1
|
303
|
+
fi
|
304
|
+
typeset info_arr=
|
305
|
+
IFS=';' read -ra info_arr <<< "$running_info"
|
306
|
+
unset IFS
|
307
|
+
|
308
|
+
typeset status=${info_arr[3]}
|
309
|
+
if [[ $status == "Up"* ]]; then
|
310
|
+
if [[ $HEALTHCHECK != "" ]]; then
|
311
|
+
healthy=`docker_healthcheck $container_id "$HEALTHCHECK"`
|
312
|
+
if [[ $healthy == "0" ]]; then
|
313
|
+
success=0
|
314
|
+
break
|
315
|
+
fi
|
316
|
+
else
|
317
|
+
if [[ $status == *"health"* ]]; then
|
318
|
+
if [[ $status == *"healthy"* ]]; then
|
319
|
+
success=0
|
320
|
+
break
|
321
|
+
fi
|
322
|
+
else
|
323
|
+
success=0
|
324
|
+
break
|
325
|
+
fi
|
326
|
+
fi
|
327
|
+
fi
|
328
|
+
|
329
|
+
if [[ `date +%s` > $end_time ]]; then
|
330
|
+
break
|
331
|
+
else
|
332
|
+
sleep 1
|
333
|
+
fi
|
334
|
+
done
|
335
|
+
|
336
|
+
if [[ $success ]]; then
|
337
|
+
echo "> container $name up: $container_id"
|
338
|
+
else
|
339
|
+
>&2 echo "container $name did not start up after $TIMEOUT seconds"
|
340
|
+
exit 1
|
341
|
+
fi
|
342
|
+
}
|
343
|
+
|
344
|
+
one_off_container() {
|
345
|
+
typeset port_args=$(docker_port_args 1)
|
346
|
+
typeset docker_cmd="/usr/bin/env docker run $port_args $DOCKER_RUN_ARGS $DOCKER_IMAGE"
|
347
|
+
if [[ $CONTAINER_NAME_PREFIX != "" ]]; then
|
348
|
+
docker_cmd="$docker_cmd --name $CONTAINER_NAME_PREFIX"
|
349
|
+
fi
|
350
|
+
if [[ $CONTAINER_BASE_HOST_NAME != "" ]]; then
|
351
|
+
docker_cmd="$docker_cmd --hostname $CONTAINER_BASE_HOST_NAME"
|
352
|
+
fi
|
353
|
+
if [[ $DOCKER_RUN_COMMAND != "" ]]; then
|
354
|
+
docker_cmd="$docker_cmd $DOCKER_RUN_COMMAND"
|
355
|
+
fi
|
356
|
+
exec $docker_cmd
|
357
|
+
}
|
358
|
+
|
359
|
+
###
|
360
|
+
### Parse command line options
|
361
|
+
###
|
362
|
+
|
363
|
+
DOCKER_IMAGE=
|
364
|
+
CONTAINER_NAME_PREFIX=
|
365
|
+
CONTAINER_COUNT=1
|
366
|
+
PORT_MAPPING=()
|
367
|
+
DOCKER_RUN_COMMAND=
|
368
|
+
HEALTHCHECK=
|
369
|
+
TIMEOUT=120
|
370
|
+
CMD_OUT=/dev/null
|
371
|
+
DOCKER_RUN_ARGS=
|
372
|
+
FORCE_RESTART=
|
373
|
+
ONE_OFF_CONTAINER=
|
374
|
+
|
375
|
+
read_arguments $@
|
376
|
+
|
377
|
+
if [[ $ONE_OFF_CONTAINER == "1" ]]; then
|
378
|
+
one_off_container
|
379
|
+
fi
|
380
|
+
|
381
|
+
if [[ $CONTAINER_NAME_PREFIX == "" ]]; then
|
382
|
+
usage
|
383
|
+
exit 1
|
384
|
+
fi
|
385
|
+
|
386
|
+
if [[ $CONTAINER_COUNT > 0 ]]; then
|
387
|
+
if [[ $DOCKER_IMAGE == "" ]]; then
|
388
|
+
usage
|
389
|
+
exit 1
|
390
|
+
else
|
391
|
+
typeset image_id=$(/usr/bin/env docker inspect --type=image --format {{.Config.Image}} $DOCKER_IMAGE)
|
392
|
+
if [[ $image_id == "" ]]; then
|
393
|
+
>&2 echo "Could not find image $DOCKER_IMAGE"
|
394
|
+
exit 1
|
395
|
+
else
|
396
|
+
echo "> using image $image_id"
|
397
|
+
fi
|
398
|
+
fi
|
399
|
+
fi
|
400
|
+
|
401
|
+
if [[ $CONTAINER_BASE_HOST_NAME == "" ]]; then
|
402
|
+
CONTAINER_BASE_HOST_NAME=$(hostname)
|
403
|
+
fi
|
404
|
+
|
405
|
+
shutdown_excess_containers
|
406
|
+
|
407
|
+
if [[ $CONTAINER_COUNT > 0 ]]; then
|
408
|
+
# Cleanup docker environment so there are no surprises
|
409
|
+
/usr/bin/env docker container prune -f > $CMD_OUT
|
410
|
+
for i in $(seq 1 $CONTAINER_COUNT); do
|
411
|
+
typeset image_id=$(container_image_id $i)
|
412
|
+
if [[ $image_id != "" && $image_id == $DOCKER_IMAGE && $FORCE_RESTART == "" ]]; then
|
413
|
+
echo "> container ${CONTAINER_NAME_PREFIX}.${i} already running $DOCKER_IMAGE"
|
414
|
+
else
|
415
|
+
stop_container $i
|
416
|
+
start_container $i
|
417
|
+
fi
|
418
|
+
done
|
419
|
+
fi
|
420
|
+
|
421
|
+
/usr/bin/env docker ps | grep -F " $CONTAINER_NAME_PREFIX." || true
|
422
|
+
|
423
|
+
exit 0
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/capistrano/docker_cluster/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "capistrano-docker_cluster"
|
7
|
+
spec.version = Capistrano::DockerCluster::VERSION
|
8
|
+
spec.authors = ["Brian Durand"]
|
9
|
+
spec.email = ["bbdurand@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{Use capistrano to deploy docker based applications.}
|
12
|
+
spec.homepage = "https://github.com/bdurand/capistrano-docker_cluster"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
# Specify which files should be added to the gem when it is released.
|
16
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
ignore_files = %w(
|
18
|
+
.gitignore
|
19
|
+
.travis.yml
|
20
|
+
Appraisals
|
21
|
+
Gemfile
|
22
|
+
Gemfile.lock
|
23
|
+
Rakefile
|
24
|
+
gemfiles/
|
25
|
+
spec/
|
26
|
+
)
|
27
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
28
|
+
`git ls-files -z`.split("\x0").reject{ |f| ignore_files.any?{ |path| f.start_with?(path) } }
|
29
|
+
end
|
30
|
+
spec.bindir = "bin"
|
31
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.required_ruby_version = '>= 2.2.2'
|
35
|
+
|
36
|
+
spec.add_dependency "capistrano", "~> 3.7"
|
37
|
+
spec.add_dependency "capistrano-scm-none", "~> 0.1"
|
38
|
+
spec.add_development_dependency "rake"
|
39
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capistrano/scm/none'
|
4
|
+
|
5
|
+
require_relative "docker_cluster/version"
|
6
|
+
require_relative "docker_cluster/scripts"
|
7
|
+
|
8
|
+
load File.expand_path("tasks/docker_cluster.rake", __dir__)
|
9
|
+
|
10
|
+
install_plugin Capistrano::Scm::None::Plugin
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module DockerCluster
|
7
|
+
class Scripts
|
8
|
+
def initialize(context)
|
9
|
+
@context = context
|
10
|
+
end
|
11
|
+
|
12
|
+
# Build a custom command line start script with the configuration arguments for
|
13
|
+
# each docker application on the host. This allows each app to be started with
|
14
|
+
# the predefined configuration by calling `bin/start app`.
|
15
|
+
def start_script(host)
|
16
|
+
apps = Array(fetch_for_host(host, :docker_apps))
|
17
|
+
cmd = "exec bin/docker-cluster"
|
18
|
+
image_id = fetch(:docker_image_id)
|
19
|
+
prefix = fetch(:docker_prefix)
|
20
|
+
|
21
|
+
cases = []
|
22
|
+
apps.each do |app|
|
23
|
+
args = app_host_args(app, host)
|
24
|
+
cases << " '#{app}')\n #{cmd} #{args.join(' ')} --name '#{prefix}#{app}' --image '#{image_id}' \"$@\"\n ;;"
|
25
|
+
end
|
26
|
+
|
27
|
+
<<~BASH
|
28
|
+
#!/usr/bin/env bash
|
29
|
+
|
30
|
+
# Generated: #{Time.now.utc.iso8601}
|
31
|
+
# Docker image tag: #{fetch(:docker_repository)}/#{fetch(:docker_tag)}
|
32
|
+
|
33
|
+
set -o errexit
|
34
|
+
|
35
|
+
cd $(dirname $0)/..
|
36
|
+
|
37
|
+
typeset app=$1
|
38
|
+
shift
|
39
|
+
|
40
|
+
case $app in
|
41
|
+
#{cases.join("\n")}
|
42
|
+
*)
|
43
|
+
>&2 echo "Usage: $0 #{apps.join('|')}"
|
44
|
+
exit 1
|
45
|
+
esac
|
46
|
+
BASH
|
47
|
+
end
|
48
|
+
|
49
|
+
# Build a custom command line run script with the configuration arguments for
|
50
|
+
# each docker application on the host to run one off containers.
|
51
|
+
def run_script(host)
|
52
|
+
# For the run script, all configured apps are included, not just the deployed ones.
|
53
|
+
# This allows configuring, for example, a "console" app to open up a console in a container.
|
54
|
+
apps = Array(fetch_for_host(host, :docker_apps))
|
55
|
+
app_configs = fetch_for_host(host, :docker_app_configs)
|
56
|
+
apps.concat(app_configs.keys) if app_configs.is_a?(Hash)
|
57
|
+
app_args = fetch_for_host(host, :docker_app_args)
|
58
|
+
apps.concat(app_args.keys) if app_args.is_a?(Hash)
|
59
|
+
apps = apps.collect(&:to_s).uniq
|
60
|
+
|
61
|
+
cmd = "exec bin/docker-cluster"
|
62
|
+
image_id = fetch(:docker_image_id)
|
63
|
+
|
64
|
+
cases = []
|
65
|
+
apps.each do |app|
|
66
|
+
args = app_host_args(app, host)
|
67
|
+
cases << " '#{app}')\n #{cmd} #{args.join(' ')} --image '#{image_id}' --one-off \"$@\"\n ;;"
|
68
|
+
end
|
69
|
+
|
70
|
+
<<~BASH
|
71
|
+
#!/usr/bin/env bash
|
72
|
+
|
73
|
+
# Generated: #{Time.now.utc.iso8601}
|
74
|
+
# Docker image tag: #{fetch(:docker_repository)}/#{fetch(:docker_tag)}
|
75
|
+
|
76
|
+
set -o errexit
|
77
|
+
|
78
|
+
cd $(dirname $0)/..
|
79
|
+
|
80
|
+
typeset app=$1
|
81
|
+
shift
|
82
|
+
|
83
|
+
case $app in
|
84
|
+
#{cases.join("\n")}
|
85
|
+
*)
|
86
|
+
>&2 echo "Usage: $0 #{apps.join('|')}"
|
87
|
+
exit 1
|
88
|
+
esac
|
89
|
+
BASH
|
90
|
+
end
|
91
|
+
|
92
|
+
# Build a custom command line stop script for each docker application on the host.
|
93
|
+
def stop_script(host)
|
94
|
+
apps = Array(fetch_for_host(host, :docker_apps))
|
95
|
+
prefix = fetch(:docker_prefix)
|
96
|
+
|
97
|
+
cases = []
|
98
|
+
all = []
|
99
|
+
apps.each do |app|
|
100
|
+
cases << " '#{app}')\n exec bin/docker-cluster --name '#{prefix}#{app}' --count 0\n ;;"
|
101
|
+
end
|
102
|
+
|
103
|
+
<<~BASH
|
104
|
+
#!/usr/bin/env bash
|
105
|
+
|
106
|
+
# Generated: #{Time.now.utc.iso8601}
|
107
|
+
# Docker image tag: #{fetch(:docker_repository)}/#{fetch(:docker_tag)}
|
108
|
+
|
109
|
+
set -o errexit
|
110
|
+
|
111
|
+
cd $(dirname $0)/..
|
112
|
+
|
113
|
+
typeset app=$1
|
114
|
+
|
115
|
+
case $app in
|
116
|
+
#{cases.join("\n")}
|
117
|
+
*)
|
118
|
+
>&2 echo "Usage: $0 #{apps.join('|')}"
|
119
|
+
exit 1
|
120
|
+
esac
|
121
|
+
BASH
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns a list of all local configuration file paths that need to be uploaded to
|
125
|
+
# the host.
|
126
|
+
def docker_config_map(host)
|
127
|
+
configs = {}
|
128
|
+
Array(fetch(:docker_configs)).each do |path|
|
129
|
+
configs[File.basename(path)] = path
|
130
|
+
end
|
131
|
+
|
132
|
+
apps = Array(fetch_for_host(host, :docker_apps))
|
133
|
+
|
134
|
+
app_configs = app_configuration(fetch(:docker_app_configs, nil))
|
135
|
+
apps.each do |app|
|
136
|
+
Array(app_configs[app.to_s]).each do |path|
|
137
|
+
configs[File.basename(path)] = path
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
Array(host.properties.docker_configs).each do |path|
|
142
|
+
configs[File.basename(path)] = path
|
143
|
+
end
|
144
|
+
|
145
|
+
host_app_configs = app_configuration(host.properties.send(:docker_app_configs))
|
146
|
+
apps.each do |app|
|
147
|
+
Array(host_app_configs[app.to_s]).each do |path|
|
148
|
+
configs[File.basename(path)] = path
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
configs
|
153
|
+
end
|
154
|
+
|
155
|
+
# Fetch a host specific property. If a the value is not defined as host specific,
|
156
|
+
# then fallback to the globally defined property.
|
157
|
+
def fetch_for_host(host, property, default = nil)
|
158
|
+
host.properties.send(property) || fetch(property, default)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# Helper to fetch a property defined in the capistrano script.
|
164
|
+
def fetch(property, default = nil)
|
165
|
+
@context.fetch(property, default)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Helper to normalize used to multiple configurations keyed by the app name
|
169
|
+
# to ensure that the keys are all strings.
|
170
|
+
def app_configuration(hash)
|
171
|
+
hash ||= {}
|
172
|
+
config = {}
|
173
|
+
hash.each do |key, value|
|
174
|
+
config[key.to_s] = value
|
175
|
+
end
|
176
|
+
config
|
177
|
+
end
|
178
|
+
|
179
|
+
# Translate a list of config file paths into command line arguments for the docker_clusther.sh command.
|
180
|
+
def config_args(config_files)
|
181
|
+
Array(config_files).collect{ |path| "--config 'config/#{File.basename(path)}'" }
|
182
|
+
end
|
183
|
+
|
184
|
+
def app_host_args(app, host)
|
185
|
+
config_args = config_args(fetch(:docker_configs, nil))
|
186
|
+
command_args = Array(fetch(:docker_args, nil))
|
187
|
+
|
188
|
+
host_config_args = config_args(host.properties.send(:docker_configs))
|
189
|
+
host_command_args = Array(host.properties.send(:"docker_args"))
|
190
|
+
|
191
|
+
app_configs = app_configuration(fetch(:docker_app_configs, {}))
|
192
|
+
app_args = app_configuration(fetch(:docker_app_args, {}))
|
193
|
+
|
194
|
+
host_app_configs = app_configuration(host.properties.send(:"docker_app_configs"))
|
195
|
+
host_app_args = app_configuration(host.properties.send(:"docker_app_args"))
|
196
|
+
|
197
|
+
app = app.to_s
|
198
|
+
app_config_args = config_args(app_configs[app])
|
199
|
+
app_command_args = Array(app_args[app])
|
200
|
+
host_app_config_args = config_args(host_app_configs[app])
|
201
|
+
host_app_command_args = Array(host_app_args[app])
|
202
|
+
args = config_args + command_args + app_config_args + app_command_args + host_config_args + host_command_args + host_app_config_args + host_app_command_args
|
203
|
+
args.uniq
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
set :docker_roles, [:docker]
|
4
|
+
|
5
|
+
def as_docker_user
|
6
|
+
user = fetch(:docker_user, nil)
|
7
|
+
if user
|
8
|
+
as(user) do
|
9
|
+
with(fetch(:docker_env, {})) do
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
else
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :deploy do
|
19
|
+
after :new_release_path, "docker:create_release"
|
20
|
+
after :updating, "docker:update"
|
21
|
+
after :published, "docker:restart"
|
22
|
+
|
23
|
+
task :upload do
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
namespace :docker do
|
28
|
+
desc "create the docker release directory and pull the image if necessary"
|
29
|
+
task :create_release do
|
30
|
+
on release_roles(fetch(:docker_roles)) do
|
31
|
+
execute :mkdir, "-p", release_path
|
32
|
+
end
|
33
|
+
|
34
|
+
if fetch(:docker_repository).include?("/")
|
35
|
+
invoke "docker:pull"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :set_image_id => :build do
|
40
|
+
on release_roles(fetch(:docker_roles)).first do
|
41
|
+
docker_tag_url = "#{fetch(:docker_repository)}:#{fetch(:docker_tag)}"
|
42
|
+
as_docker_user do
|
43
|
+
image_id = capture(:docker, "image", "ls", "--no-trunc", "--format", "'{{.ID}}'", docker_tag_url)
|
44
|
+
set :docker_image_id, image_id[7, 12]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Build and tag the docker image. This task does nothing by default, but can be implemented where needed."
|
50
|
+
task :build do
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Update the configuration and command line arguments for running a docker deployment."
|
54
|
+
task :update => :set_image_id do
|
55
|
+
invoke("docker:copy_configs")
|
56
|
+
invoke("docker:upload_commands")
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Prune the docker engine of all dangling images, containers, and volumes."
|
60
|
+
task :prune do
|
61
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
62
|
+
as_docker_user do
|
63
|
+
execute :docker, "system", "prune", "--force"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Pull a tag from a remote into the local docker engine. If the task docker:authenticate is defined, it will be invoked first."
|
69
|
+
task :pull do
|
70
|
+
docker_tag_url = "#{fetch(:docker_repository)}:#{fetch(:docker_tag)}"
|
71
|
+
if Rake::Task.task_defined?('docker:authenticate')
|
72
|
+
invoke "docker:authenticate"
|
73
|
+
end
|
74
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
75
|
+
as_docker_user do
|
76
|
+
execute :docker, "pull", docker_tag_url
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Restart the docker containers (alias to docker:start)."
|
82
|
+
task :restart do
|
83
|
+
invoke("docker:start")
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Restart the docker containers."
|
87
|
+
task :start do
|
88
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
89
|
+
within "#{fetch(:deploy_to)}/current" do
|
90
|
+
scripts = Capistrano::DockerCluster::Scripts.new(self)
|
91
|
+
Array(scripts.fetch_for_host(host, :docker_apps)).each do |app|
|
92
|
+
as_docker_user do
|
93
|
+
execute "bin/start", app
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
desc "Stop the docker containers."
|
101
|
+
task :stop do
|
102
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
103
|
+
within "#{fetch(:deploy_to)}/current" do
|
104
|
+
scripts = Capistrano::DockerCluster::Scripts.new(self)
|
105
|
+
Array(scripts.fetch_for_host(host, :docker_apps)).each do |app|
|
106
|
+
as_docker_user do
|
107
|
+
execute "bin/stop", app
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "Upload the commands to stop and start the application docker containers."
|
115
|
+
task :upload_commands do
|
116
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
117
|
+
within fetch(:release_path) do
|
118
|
+
execute(:mkdir, "-p", "bin")
|
119
|
+
scripts = Capistrano::DockerCluster::Scripts.new(self)
|
120
|
+
docker_cluster_path = File.join(__dir__, "..", "..", "..", "bin", "docker-cluster")
|
121
|
+
upload! docker_cluster_path, "bin/docker-cluster"
|
122
|
+
upload! StringIO.new(scripts.start_script(host)), "bin/start"
|
123
|
+
upload! StringIO.new(scripts.stop_script(host)), "bin/stop"
|
124
|
+
upload! StringIO.new(scripts.run_script(host)), "bin/run"
|
125
|
+
execute :chmod, "a+x", "bin/*"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "Copy configuration files used to start the docker containers."
|
131
|
+
task :copy_configs do
|
132
|
+
on release_roles(fetch(:docker_roles)) do |host|
|
133
|
+
configs = Capistrano::DockerCluster::Scripts.new(self).docker_config_map(host)
|
134
|
+
unless configs.empty?
|
135
|
+
within fetch(:release_path) do
|
136
|
+
execute(:mkdir, "-p", "config")
|
137
|
+
configs.each do |name, local_path|
|
138
|
+
upload! local_path, "config/#{name}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-docker_cluster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Durand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: capistrano
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: capistrano-scm-none
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- bbdurand@gmail.com
|
58
|
+
executables:
|
59
|
+
- docker-cluster
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- CHANGE_LOG.md
|
64
|
+
- MIT_LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- bin/docker-cluster
|
67
|
+
- capistrano-docker_cluster.gemspec
|
68
|
+
- lib/capistrano/docker_cluster.rb
|
69
|
+
- lib/capistrano/docker_cluster/scripts.rb
|
70
|
+
- lib/capistrano/docker_cluster/version.rb
|
71
|
+
- lib/capistrano/tasks/docker_cluster.rake
|
72
|
+
homepage: https://github.com/bdurand/capistrano-docker_cluster
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 2.2.2
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubygems_version: 3.0.3
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Use capistrano to deploy docker based applications.
|
95
|
+
test_files: []
|