├── .gitignore ├── Makefile ├── README.md ├── Vagrantfile ├── commands ├── install ├── internal-functions ├── jobs ├── hashi-ui.hcl └── traefik.hcl ├── plugin.toml ├── post-create ├── post-delete ├── report ├── scheduler-deploy ├── scheduler-logs-failed ├── scheduler-run ├── scheduler-stop ├── scheduler-tags-create ├── scheduler-tags-destroy ├── subcommands ├── default ├── report └── set └── templates ├── background.hcl.sigil └── web.hcl.sigil /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dig 2 | dig: 3 | dig +short consul.service.consul SRV 4 | dig @127.0.0.1 -p 53 +short consul.service.consul SRV 5 | sudo systemctl restart systemd-resolved.service 6 | dig +short consul.service.consul SRV 7 | dig @127.0.0.1 -p 53 +short consul.service.consul SRV 8 | 9 | .PHONY: nomad-jobs 10 | nomad-jobs: 11 | sleep 10 12 | levant deploy -force-count jobs/hashi-ui.hcl 13 | levant deploy -force-count jobs/traefik.hcl 14 | 15 | .PHONY: restart-consul 16 | restart-consul: 17 | systemctl daemon-reload && sudo systemctl restart consul.service 18 | 19 | .PHONY: restart-nomad 20 | restart-nomad: 21 | systemctl daemon-reload && sudo systemctl restart nomad.service 22 | 23 | .PHONY: clear 24 | clear: stop-all clear-all start-all 25 | 26 | .PHONY: clear-all 27 | clear-all: clear-consul clear-nomad 28 | 29 | .PHONY: clear-consul 30 | clear-consul: 31 | sudo rm -rf /var/lib/consul/* 32 | 33 | .PHONY: clear-nomad 34 | clear-nomad: 35 | sudo rm -rf /var/lib/nomad/* 36 | 37 | .PHONY: start-all 38 | start-all: start-consul start-nomad 39 | 40 | .PHONY: start-consul 41 | start-consul: 42 | sudo systemctl start consul.service 43 | 44 | .PHONY: start-nomad 45 | start-nomad: 46 | sudo systemctl start nomad.service 47 | 48 | .PHONY: stop-all 49 | stop-all: stop-consul stop-nomad 50 | 51 | .PHONY: stop-consul 52 | stop-consul: 53 | sudo systemctl stop consul.service 54 | 55 | .PHONY: stop-nomad 56 | stop-nomad: 57 | sudo systemctl stop nomad.service 58 | 59 | .PHONY: sync 60 | sync: 61 | sudo mkdir -p /var/lib/dokku/plugins/available/scheduler-nomad 62 | sudo rsync -a /vagrant/ /var/lib/dokku/plugins/available/scheduler-nomad --exclude .vagrant --exclude .git --exclude jobs --exclude Vagrantfile 63 | sudo chown -R dokku:dokku /var/lib/dokku/plugins/available/scheduler-nomad 64 | sudo dokku plugin:disable scheduler-nomad || true 65 | sudo dokku plugin:enable scheduler-nomad 66 | sudo dokku plugin:install 67 | dokku config:set --global DOKKU_SCHEDULER=nomad 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dokku-scheduler-nomad 2 | 3 | > If this plugin is missing a feature you need, consider [sponsoring development](https://github.com/dokku/.github/blob/master/SPONSORING.md). Pull requests always welcome! 4 | 5 | A Dokku plugin to integrate with nomad. 6 | 7 | ## Testing 8 | 9 | You can start a local testing server. This will: 10 | 11 | - install consul 12 | - install docker 13 | - install levant (for deploy) 14 | - install nomad 15 | - install dokku in unattended mode with nginx disabled 16 | - install the dokku-clone plugin 17 | - install the dokku-registry plugin 18 | - start hashi-ui and traefik in the nomad cluster 19 | - set nomad as the scheduler 20 | 21 | To start the server, you'll need to set some arguments to log into quay.io 22 | 23 | ```shell 24 | # start the server 25 | vagrant --quay-username="SOME_USERNAME" --quay-password="SOME_PASSWORD" up 26 | ``` 27 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | require 'getoptlong' 5 | 6 | quay_username = '' 7 | quay_password = '' 8 | 9 | if ARGV.include?('up') || ARGV.include?('provision') 10 | opts = GetoptLong.new( 11 | [ '--quay-password', GetoptLong::REQUIRED_ARGUMENT ], 12 | [ '--quay-username', GetoptLong::REQUIRED_ARGUMENT ] 13 | ) 14 | 15 | opts.each do |opt, arg| 16 | case opt 17 | when '--quay-password' 18 | quay_password=arg 19 | when '--quay-username' 20 | quay_username=arg 21 | end 22 | end 23 | end 24 | 25 | SSH_KEY = File.expand_path(ENV.fetch('VAGRANT_SSH_KEY', '~/.ssh/id_rsa')) 26 | if !File.exist?(SSH_KEY) 27 | error "ERROR: Please create an ssh key at the path #{SSH_KEY}" 28 | exit 1 29 | end 30 | 31 | if File.readlines(SSH_KEY).grep(/ENCRYPTED/).size > 0 32 | error "ERROR: GitHub SSH Key at #{SSH_KEY} contains a passphrase." 33 | error 'You need to generate a new key without a passphrase manually.' 34 | error 'See the vm documentation for more details.' 35 | error 'You can also override it\'s location with the environment variable VAGRANT_SSH_KEY' 36 | exit 1 37 | end 38 | 39 | 40 | $script = <<-SCRIPT 41 | ADVERTISE_ADDR="$(ifconfig eth0 | grep "inet " | xargs | cut -d' ' -f2)" 42 | 43 | # Update apt and get dependencies 44 | sudo apt-get update > /dev/null 45 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -qq -o=Dpkg::Use-Pty=0 -y unzip curl vim \ 46 | apt-transport-https \ 47 | ca-certificates \ 48 | software-properties-common 49 | 50 | pushd /tmp/ > /dev/null 51 | 52 | if [[ ! -f nomad.zip ]]; then 53 | echo "Fetching Nomad..." 54 | NOMAD_VERSION=0.9.0 55 | curl -sSL https://releases.hashicorp.com/nomad/${NOMAD_VERSION}/nomad_${NOMAD_VERSION}_linux_amd64.zip -o nomad.zip 56 | fi 57 | 58 | if [[ ! -f consul.zip ]]; then 59 | echo "Fetching Consul..." 60 | CONSUL_VERSION=1.4.4 61 | curl -sSL https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip -o consul.zip 62 | fi 63 | 64 | if [[ ! -f levant ]]; then 65 | echo "Fetching Levant..." 66 | LEVANT_VERSION=0.2.7 67 | curl -sL https://github.com/jrasell/levant/releases/download/${LEVANT_VERSION}/linux-amd64-levant -o levant 68 | fi 69 | 70 | sudo mkdir -p /etc/nomad.d 71 | sudo chmod a+w /etc/nomad.d 72 | 73 | # Set hostname's IP to make advertisement Just Work 74 | #sudo sed -i -e "s/.*nomad.*/$(ip route get 1 | awk '{print $NF;exit}') nomad/" /etc/hosts 75 | 76 | echo "Installing Docker..." 77 | if [[ -f /etc/apt/sources.list.d/docker.list ]]; then 78 | echo "Docker repository already installed; Skipping" 79 | elif grep -q docker.com /etc/apt/sources.list; then 80 | echo "Docker repository already installed; Skipping" 81 | else 82 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o docker.gpgkey 83 | sudo apt-key add docker.gpgkey 84 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 85 | sudo apt-get update > /dev/null 86 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -qq -o=Dpkg::Use-Pty=0 -y docker-ce 87 | 88 | # Restart docker to make sure we get the latest version of the daemon if there is an upgrade 89 | sudo service docker restart 90 | fi 91 | 92 | 93 | if [[ ! -f /home/vagrant/.docker/config.json ]] || ! grep -q quay.io /home/vagrant/.docker/config.json; then 94 | echo "#{quay_password}" | sudo docker login -u="#{quay_username}" --password-stdin quay.io 95 | sudo mkdir -p /root/.docker 96 | sudo cp /home/vagrant/.docker/config.json /root/.docker/config.json 97 | sudo chown -R vagrant:vagrant /home/vagrant/.docker 98 | sudo chown -R root:root /root/.docker 99 | fi 100 | 101 | # Make sure we can actually use docker as the vagrant user 102 | sudo usermod -aG docker vagrant 103 | 104 | echo "Installing Consul..." 105 | test -f consul || unzip /tmp/consul.zip 106 | sudo install consul /usr/bin/consul 107 | sudo mkdir -p /etc/consul.d /var/lib/consul 108 | ( 109 | cat <<-EOF 110 | { 111 | "addresses": { 112 | "dns": "127.0.0.1" 113 | }, 114 | "advertise_addr": "ADVERTISE_ADDR", 115 | "bind_addr": "0.0.0.0", 116 | "client_addr": "0.0.0.0", 117 | "data_dir": "/var/lib/consul/", 118 | "datacenter": "dc1", 119 | "dns_config": { 120 | "only_passing": true, 121 | "allow_stale": true, 122 | "node_ttl": "60s", 123 | "service_ttl": { 124 | "*": "10s" 125 | } 126 | }, 127 | "enable_syslog": true, 128 | "disable_remote_exec": true, 129 | "disable_update_check": true, 130 | "leave_on_terminate": true, 131 | "performance": { 132 | "raft_multiplier": 1 133 | }, 134 | "ports": { 135 | "dns": 53 136 | }, 137 | "recursors":[ 138 | "8.8.8.8" 139 | ], 140 | "rejoin_after_leave": true, 141 | "skip_leave_on_interrupt": false, 142 | "bootstrap_expect": 1, 143 | "server": true, 144 | "ui": true 145 | } 146 | EOF 147 | ) | sed "s/ADVERTISE_ADDR/$ADVERTISE_ADDR/" | sudo tee /etc/consul.d/default.json > /dev/null 148 | 149 | ( 150 | cat <<-EOF 151 | [Unit] 152 | Description=consul agent 153 | Requires=network-online.target 154 | After=network-online.target 155 | 156 | [Service] 157 | Restart=on-failure 158 | ExecStart=/usr/bin/consul agent -config-file /etc/consul.d/default.json 159 | ExecReload=/bin/kill -HUP $MAINPID 160 | 161 | [Install] 162 | WantedBy=multi-user.target 163 | EOF 164 | ) | sudo tee /etc/systemd/system/consul.service > /dev/null 165 | sudo systemctl enable consul.service 166 | sudo systemctl start consul 167 | 168 | # systemctl daemon-reload && sudo systemctl restart consul.service 169 | while [[ "$(curl -s -o /dev/null --connect-timeout 1 --max-time 1 -w ''%{http_code}'' localhost:8500/v1/status/leader)" != "200" ]]; do sleep 1; done 170 | 171 | echo "Updating systemd-resolved" 172 | ( 173 | cat <<-EOF 174 | [Resolve] 175 | DNS=127.0.0.1 176 | Domains=~consul 177 | EOF 178 | ) | sudo tee /etc/systemd/resolved.conf > /dev/null 179 | sudo systemctl restart systemd-resolved.service 180 | 181 | echo "Installing Nomad..." 182 | test -f nomad || unzip nomad.zip 183 | sudo install nomad /usr/bin/nomad 184 | sudo mkdir -p /var/lib/nomad 185 | ( 186 | cat <<-EOF 187 | { 188 | "bind_addr": "0.0.0.0", 189 | "data_dir": "/var/lib/nomad", 190 | "datacenter": "dc1", 191 | "disable_update_check": true, 192 | "enable_syslog": true, 193 | "leave_on_interrupt": true, 194 | "leave_on_terminate": true, 195 | "log_level": "DEBUG", 196 | "name": "", 197 | "addresses": { 198 | "http": "0.0.0.0", 199 | "rpc": "0.0.0.0", 200 | "serf": "0.0.0.0" 201 | }, 202 | "advertise": { 203 | "http": "ADVERTISE_ADDR:4646", 204 | "rpc": "ADVERTISE_ADDR:4647", 205 | "serf": "ADVERTISE_ADDR:4648" 206 | }, 207 | "client": { 208 | "enabled": true, 209 | "network_interface": "eth0", 210 | "node_class": "nomad-server", 211 | "max_kill_timeout": "300s", 212 | "options": { 213 | "docker.auth.config": "/root/.docker/config.json", 214 | "docker.cleanup.image.delay": "1h", 215 | "driver.raw_exec.enable": true 216 | }, 217 | "reserved": { 218 | "reserved_ports": "22,25,53,123,514,4646-4648,48484,49968,8200-8302,8400,8500,8600,8953" 219 | } 220 | }, 221 | "consul": { 222 | "address": "127.0.0.1:8500", 223 | "client_auto_join": true, 224 | "client_service_name": "nomad-client", 225 | "server_auto_join": true, 226 | "server_service_name": "nomad" 227 | }, 228 | "server": { 229 | "enabled": true, 230 | "bootstrap_expect": 1 231 | } 232 | } 233 | EOF 234 | ) | sed "s/ADVERTISE_ADDR/$ADVERTISE_ADDR/" | sudo tee /etc/nomad.d/default.json > /dev/null 235 | 236 | ( 237 | cat <<-EOF 238 | [Unit] 239 | Description=nomad agent 240 | Requires=network-online.target 241 | After=network-online.target consul.service 242 | 243 | [Service] 244 | Restart=on-failure 245 | ExecStart=/usr/bin/nomad agent -config /etc/nomad.d/default.json 246 | ExecReload=/bin/kill -HUP $MAINPID 247 | 248 | [Install] 249 | WantedBy=multi-user.target 250 | EOF 251 | ) | sudo tee /etc/systemd/system/nomad.service > /dev/null 252 | sudo systemctl enable nomad.service 253 | sudo systemctl start nomad 254 | while [[ "$(curl -s -o /dev/null --connect-timeout 1 --max-time 1 -w ''%{http_code}'' localhost:4646/v1/status/leader)" != "200" ]]; do sleep 1; done 255 | 256 | # systemctl daemon-reload && sudo systemctl restart nomad.service 257 | 258 | echo "Installing Levant..." 259 | chmod +x levant 260 | sudo install levant /usr/bin/levant 261 | 262 | for bin in cfssl cfssl-certinfo cfssljson 263 | do 264 | echo "Installing $bin..." 265 | curl -sSL https://pkg.cfssl.org/R1.2/${bin}_linux-amd64 > /tmp/${bin} 266 | sudo install /tmp/${bin} /usr/local/bin/${bin} 267 | done 268 | 269 | if ! grep -q nomad /home/vagrant/.bashrc; then 270 | echo "Installing autocomplete..." 271 | nomad -autocomplete-install 272 | fi 273 | 274 | echo "Installing Dokku" 275 | export SOURCE="https://packagecloud.io/dokku/dokku-betafish/ubuntu/" 276 | export OS_ID="$(lsb_release -cs 2> /dev/null || echo "trusty")" 277 | 278 | # curl -fsSL https://packagecloud.io/dokku/dokku-betafish/gpgkey -o dokku-betafish.gpgkey 279 | # sudo apt-key add dokku-betafish.gpgkey 280 | # echo "utopicvividwilyxenialyakketyzestyartfulbionic" | grep -q "$OS_ID" || OS_ID="trusty" 281 | # echo "deb $SOURCE $OS_ID main" | sudo tee /etc/apt/sources.list.d/dokku-betafish.list > /dev/null 282 | 283 | curl -fsSL https://packagecloud.io/dokku/dokku/gpgkey -o dokku.gpgkey 284 | sudo apt-key add dokku.gpgkey 285 | export SOURCE="https://packagecloud.io/dokku/dokku/ubuntu/" 286 | echo "deb $SOURCE $OS_ID main" | sudo tee /etc/apt/sources.list.d/dokku.list > /dev/null 287 | 288 | sudo apt-get update > /dev/null 289 | 290 | echo "dokku dokku/web_config boolean false" | sudo debconf-set-selections 291 | echo "dokku dokku/vhost_enable boolean true" | sudo debconf-set-selections 292 | echo "dokku dokku/hostname string dokku.me" | sudo debconf-set-selections 293 | echo "dokku dokku/skip_key_file boolean true" | sudo debconf-set-selections 294 | echo "dokku dokku/key_file string /root/.ssh/id_rsa.pub" | sudo debconf-set-selections 295 | echo "dokku dokku/nginx_enable boolean false" | sudo debconf-set-selections 296 | 297 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -qq -o=Dpkg::Use-Pty=0 -y dokku 298 | sudo dokku plugin:install-dependencies --core 299 | grep -q clone <(dokku plugin:list) || sudo dokku plugin:install https://github.com/crisward/dokku-clone.git clone 300 | grep -q registry <(dokku plugin:list) || sudo dokku plugin:install https://github.com/dokku/dokku-registry.git registry 301 | 302 | sudo mkdir -p /home/dokku/.docker 303 | sudo cp /home/vagrant/.docker/config.json /home/dokku/.docker/config.json 304 | sudo chown -R vagrant:vagrant /home/vagrant/.docker 305 | sudo chown -R dokku:dokku /home/dokku/.docker 306 | 307 | sudo systemctl stop nginx.service 308 | sudo systemctl disable nginx.service 309 | 310 | echo "Pushing required jobs" 311 | pushd /vagrant > /dev/null 312 | make nomad-jobs 313 | 314 | echo "Syncing plugin" 315 | make sync 316 | 317 | dokku clone:allow github.com 318 | dokku apps:create python-sample 319 | dokku registry:set python-sample server quay.io/ 320 | dokku registry:set python-sample image-repo dokku/python-sample 321 | dokku registry:set python-sample username dokku 322 | sudo apt-get install -qq -o=Dpkg::Use-Pty=0 -y asciinema 323 | 324 | # dokku clone python-sample https://github.com/josegonzalez/python-sample.git 325 | 326 | SCRIPT 327 | 328 | Vagrant.configure(2) do |config| 329 | config.vm.box = "bento/ubuntu-18.04" # 18.04 LTS 330 | config.vm.hostname = "nomad" 331 | config.vm.provision "shell", inline: $script, privileged: false 332 | config.vm.provision "docker" # Just install it 333 | 334 | if File.exists?(SSH_KEY) 335 | ssh_key = File.read(SSH_KEY) 336 | config.vm.provision :shell, :inline => "echo 'Copying local GitHub SSH Key to VM for provisioning...' && mkdir -p /root/.ssh && echo '#{ssh_key}' > /root/.ssh/id_rsa && chmod 600 /root/.ssh/id_rsa" 337 | config.vm.provision :shell, :inline => "echo 'Copying local GitHub SSH Key to VM for provisioning...' && mkdir -p /home/vagrant/.ssh && echo '#{ssh_key}' > /home/vagrant/.ssh/id_rsa && chmod 600 /home/vagrant/.ssh/id_rsa && chown -R vagrant:vagrant /home/vagrant/.ssh" 338 | else 339 | raise Vagrant::Errors::VagrantError, "\n\nERROR: GitHub SSH Key not found at ~/.ssh/id_rsa.\nYou can generate this key manually.\nYou can also override it with the environment variable VAGRANT_SSH_KEY\n\n" 340 | end 341 | 342 | config.vm.provision :shell do |shell| 343 | shell.inline = "echo 'Ensuring sudo commands have access to local SSH keys' && touch $1 && chmod 0440 $1 && echo $2 > $1" 344 | shell.args = %q{/etc/sudoers.d/root_ssh_agent "Defaults env_keep += \"SSH_AUTH_SOCK\""} 345 | end 346 | 347 | # Expose the traefik api and ui to the host 348 | config.vm.network "forwarded_port", guest: 80, host: 8080, auto_correct: true 349 | config.vm.network "forwarded_port", guest: 81, host: 8081, auto_correct: true 350 | # Expose the hashi-ui api and ui to the host 351 | config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true 352 | # Expose the nomad api and ui to the host 353 | config.vm.network "forwarded_port", guest: 4646, host: 4646, auto_correct: true 354 | # Expose the consul api and ui to the host 355 | config.vm.network "forwarded_port", guest: 8500, host: 8500, auto_correct: true 356 | 357 | # Increase memory for Parallels Desktop 358 | config.vm.provider "parallels" do |p, o| 359 | p.memory = "1024" 360 | end 361 | 362 | # Increase memory for Virtualbox 363 | config.vm.provider "virtualbox" do |vb| 364 | vb.memory = "1024" 365 | end 366 | 367 | # Increase memory for VMware 368 | ["vmware_fusion", "vmware_workstation"].each do |p| 369 | config.vm.provider p do |v| 370 | v.vmx["memsize"] = "1024" 371 | end 372 | end 373 | end 374 | -------------------------------------------------------------------------------- /commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_AVAILABLE_PATH/scheduler-nomad/internal-functions" 4 | 5 | case "$1" in 6 | help | scheduler-nomad:help) 7 | cmd-scheduler-nomad-help "$@" 8 | ;; 9 | 10 | *) 11 | exit "$DOKKU_NOT_IMPLEMENTED_EXIT" 12 | ;; 13 | 14 | esac 15 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | 5 | scheduler-nomad-install() { 6 | declare desc="scheduler-nomad install plugin trigger" 7 | declare trigger="scheduler-nomad-install" 8 | 9 | mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-nomad" 10 | chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/scheduler-nomad" 11 | 12 | fn-plugin-property-setup "scheduler-nomad" 13 | } 14 | 15 | scheduler-nomad-install "$@" 16 | -------------------------------------------------------------------------------- /internal-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 5 | 6 | cmd-scheduler-nomad-report() { 7 | declare desc="displays a scheduler-nomad report for one or more apps" 8 | local cmd="scheduler-nomad:report" 9 | local INSTALLED_APPS=$(dokku_apps) 10 | local APP="$2" INFO_FLAG="$3" 11 | 12 | if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then 13 | INFO_FLAG="$APP" 14 | APP="" 15 | fi 16 | 17 | if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then 18 | INFO_FLAG="true" 19 | fi 20 | 21 | if [[ -z "$APP" ]]; then 22 | for app in $INSTALLED_APPS; do 23 | cmd-scheduler-nomad-report-single "$app" "$INFO_FLAG" | tee || true 24 | done 25 | else 26 | cmd-scheduler-nomad-report-single "$APP" "$INFO_FLAG" 27 | fi 28 | } 29 | 30 | cmd-scheduler-nomad-report-single() { 31 | declare APP="$1" INFO_FLAG="$2" 32 | if [[ "$INFO_FLAG" == "true" ]]; then 33 | INFO_FLAG="" 34 | fi 35 | verify_app_name "$APP" 36 | local flag_map=( 37 | "--scheduler-nomad-addr: $(fn-plugin-property-get "scheduler-nomad" "$APP" "nomad-addr" "")" 38 | "--scheduler-nomad-datacenter: $(fn-plugin-property-get "scheduler-nomad" "$APP" "nomad-datacenter" "dc1")" 39 | "--scheduler-levant-version: $(levant version | head -n1)" 40 | ) 41 | 42 | if [[ -z "$INFO_FLAG" ]]; then 43 | dokku_log_info2_quiet "${APP} scheduler-nomad information" 44 | for flag in "${flag_map[@]}"; do 45 | key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" 46 | dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" 47 | done 48 | else 49 | local match=false; local value_exists=false 50 | for flag in "${flag_map[@]}"; do 51 | valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" 52 | if [[ "$flag" == "${INFO_FLAG}:"* ]]; then 53 | value=${flag#*: } 54 | size="${#value}" 55 | if [[ "$size" -ne 0 ]]; then 56 | echo "$value" && match=true && value_exists=true 57 | else 58 | match=true 59 | fi 60 | fi 61 | done 62 | [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" 63 | [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" 64 | fi 65 | } 66 | 67 | scheduler_docker_local_help_content_func() { 68 | declare desc="return scheduler-nomad plugin help content" 69 | cat<] [], Displays a scheduler-nomad report for one or more apps 71 | scheduler-nomad:set (), Set or clear a scheduler-nomad property for an app 72 | help_content 73 | } 74 | 75 | cmd-scheduler-nomad-help() { 76 | if [[ $1 = "scheduler-nomad:help" ]] ; then 77 | echo -e 'Usage: dokku scheduler-nomad[:COMMAND]' 78 | echo '' 79 | echo 'Manages the scheduler-nomad integration for an app.' 80 | echo '' 81 | echo 'Additional commands:' 82 | scheduler_docker_local_help_content_func | sort | column -c2 -t -s, 83 | echo '' 84 | elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then 85 | scheduler_docker_local_help_content_func 86 | else 87 | cat< /dev/null' RETURN INT TERM EXIT 23 | 24 | dokku_log_info2 "Deploying via nomad" 25 | IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") 26 | plugn trigger pre-deploy "$APP" "$IMAGE_TAG" 27 | 28 | ENV_VAR_NAME="$(fn-plugin-property-get "git" "$APP" "rev-env-var")" 29 | if [[ -z "$ENV_VAR_NAME" ]] && ! fn-plugin-property-exists "git" "$APP" "rev-env-var"; then 30 | ENV_VAR_NAME="GIT_REV" 31 | fi 32 | GIT_REV=$(config_get "$APP" "$ENV_VAR_NAME") 33 | 34 | _NOMAD_ADDR=$(fn-plugin-property-get "scheduler-nomad" "$APP" "nomad-addr" "") 35 | [[ -n "$_NOMAD_ADDR" ]] && export NOMAD_ADDR="$_NOMAD_ADDR" 36 | 37 | while read -r line || [[ -n "$line" ]]; do 38 | [[ "$line" =~ ^#.* ]] && continue 39 | line="$(strip_inline_comments "$line")" 40 | PROC_TYPE=${line%%=*} 41 | PROC_COUNT=${line#*=} 42 | 43 | dokku_log_info1 "Deploying ${PROC_TYPE} to ${PROC_COUNT}" 44 | 45 | # todo: pull out all of these configuration variables 46 | DOKKU_CHECK_INTERVAL=10 47 | DOKKU_CHECK_PATH=/ 48 | DOKKU_CHECKS_TIMEOUT=30 49 | DOKKU_DOCKER_STOP_TIMEOUT=10 50 | DOKKU_WAIT_TO_RETIRE=10 51 | RESOURCE_CPU=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "reserve" "cpu" || echo 100) 52 | RESOURCE_MEMORY=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "limit" "memory" || echo 256) 53 | RESOURCE_NETWORK_MBITS=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "limit" "network" || echo 10) 54 | NOMAD_DATACENTER=$(fn-plugin-property-get "scheduler-nomad" "$APP" "nomad-datacenter" "dc1") 55 | 56 | local template="$PLUGIN_AVAILABLE_PATH/scheduler-nomad/templates/web.hcl.sigil" 57 | [[ "$PROC_TYPE" != web ]] && template="$PLUGIN_AVAILABLE_PATH/scheduler-nomad/templates/background.hcl.sigil" 58 | 59 | # TODO: figure out a way to shim in environment variables 60 | SIGIL_PARAMS=(-f "$template" APP="$APP" 61 | CHECK_INTERVAL="$DOKKU_CHECK_INTERVAL" CHECK_TIMEOUT="$DOKKU_CHECKS_TIMEOUT" CHECK_PATH="$DOKKU_CHECK_PATH" 62 | RESOURCE_CPU="$RESOURCE_CPU" RESOURCE_MEMORY="$RESOURCE_MEMORY" 63 | RESOURCE_NETWORK_MBITS="$RESOURCE_NETWORK_MBITS" 64 | DATACENTER="$NOMAD_DATACENTER" IMAGE="$IMAGE" 65 | ENVIRONMENT_VARIABLES="" 66 | GIT_REV="$GIT_REV" DEPLOYMENT_ID="$DEPLOYMENT_ID" 67 | KILL_TIMEOUT="$DOKKU_DOCKER_STOP_TIMEOUT" SHUTDOWN_DELAY="$DOKKU_WAIT_TO_RETIRE" 68 | PROCESS_COUNT="$PROC_COUNT" PROCESS_TYPE="$PROC_TYPE") 69 | 70 | sigil "${SIGIL_PARAMS[@]}" | cat -s > $TMP_JOB_FILE 71 | 72 | levant deploy -force-count $TMP_JOB_FILE | sed "s/^/ /" 73 | done < "$DOKKU_SCALE_FILE" 74 | 75 | dokku_log_info2 "Deploy complete" 76 | 77 | dokku_log_info1 "Running post-deploy" 78 | plugn trigger core-post-deploy "$APP" "" "" "$IMAGE_TAG" 79 | plugn trigger post-deploy "$APP" "" "" "$IMAGE_TAG" 80 | } 81 | 82 | scheduler-nomad-scheduler-deploy "$@" 83 | -------------------------------------------------------------------------------- /scheduler-logs-failed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 4 | 5 | scheduler-nomad-scheduler-logs-failed() { 6 | declare desc="scheduler-nomad scheduler-logs-failed plugin trigger" 7 | declare trigger="scheduler-nomad scheduler-logs-failed" 8 | declare DOKKU_SCHEDULER="$1" APP="$2" 9 | shift 2 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for nomad" 16 | } 17 | 18 | scheduler-nomad-scheduler-logs-failed "$@" 19 | -------------------------------------------------------------------------------- /scheduler-run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 4 | 5 | scheduler-nomad-scheduler-run() { 6 | declare desc="scheduler-nomad scheduler-run plugin trigger" 7 | declare trigger="scheduler-nomad scheduler-run" 8 | declare DOKKU_SCHEDULER="$1" APP="$2" 9 | shift 2 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for nomad" 16 | } 17 | 18 | scheduler-nomad-scheduler-run "$@" 19 | -------------------------------------------------------------------------------- /scheduler-stop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 5 | 6 | scheduler-nomad-scheduler-stop() { 7 | declare desc="scheduler-nomad scheduler-stop plugin trigger" 8 | declare trigger="scheduler-nomad scheduler-stop" 9 | declare DOKKU_SCHEDULER="$1" APP="$2" REMOVE_CONTAINERS="$3" 10 | shift 2 11 | 12 | if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then 13 | return 14 | fi 15 | 16 | local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" 17 | if [[ ! -f "$DOKKU_SCALE_FILE" ]]; then 18 | return 19 | fi 20 | 21 | _NOMAD_ADDR=$(fn-plugin-property-get "scheduler-nomad" "$APP" "nomad-addr" "") 22 | [[ -n "$_NOMAD_ADDR" ]] && export NOMAD_ADDR="$_NOMAD_ADDR" 23 | 24 | while read -r line || [[ -n "$line" ]]; do 25 | [[ "$line" =~ ^#.* ]] && continue 26 | line="$(strip_inline_comments "$line")" 27 | PROC_TYPE=${line%%=*} 28 | 29 | levant scale-in -percent 100 -task-group "${APP}-${PROC_TYPE}" "${APP}-${PROC_TYPE}" 30 | done < "$DOKKU_SCALE_FILE" 31 | } 32 | 33 | scheduler-nomad-scheduler-stop "$@" 34 | -------------------------------------------------------------------------------- /scheduler-tags-create: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 4 | 5 | scheduler-nomad-scheduler-tags-create() { 6 | declare desc="scheduler-nomad scheduler-tags-create plugin trigger" 7 | declare trigger="scheduler-nomad scheduler-tags-create" 8 | declare DOKKU_SCHEDULER="$1" APP="$2" SOURCE_IMAGE="$3" TARGET_IMAGE="$4" 9 | shift 2 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for nomad" 16 | } 17 | 18 | scheduler-nomad-scheduler-tags-create "$@" 19 | -------------------------------------------------------------------------------- /scheduler-tags-destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 4 | 5 | scheduler-nomad-scheduler-tags-destroy() { 6 | declare desc="scheduler-nomad scheduler-tags-destroy plugin trigger" 7 | declare trigger="scheduler-nomad scheduler-tags-destroy" 8 | declare DOKKU_SCHEDULER="$1" APP="$2" IMAGE_REPO="$3" IMAGE_TAG="$4" 9 | shift 2 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for nomad" 16 | } 17 | 18 | scheduler-nomad-scheduler-tags-destroy "$@" 19 | -------------------------------------------------------------------------------- /subcommands/default: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 3 | source "$PLUGIN_AVAILABLE_PATH/scheduler-nomad/internal-functions" 4 | 5 | cmd-scheduler-nomad-help "scheduler-nomad:help" 6 | -------------------------------------------------------------------------------- /subcommands/report: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-nomad/internal-functions" 3 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 4 | 5 | cmd-scheduler-nomad-report "$@" 6 | -------------------------------------------------------------------------------- /subcommands/set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 5 | 6 | fn-in-array() { 7 | declare desc="return true if value ($1) is in list (all other arguments)" 8 | 9 | local e 10 | for e in "${@:2}"; do 11 | [[ "$e" == "$1" ]] && return 0 12 | done 13 | return 1 14 | } 15 | 16 | scheduler-nomad-set-cmd() { 17 | declare desc="set or clear a scheduler-nomad property for an app" 18 | local cmd="scheduler-nomad:set" argv=("$@"); [[ ${argv[0]} == "$cmd" ]] && shift 1 19 | declare APP="$1" KEY="$2" VALUE="$3" 20 | local VALID_KEYS=("nomad-addr" "nomad-datacenter") 21 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 22 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 23 | 24 | if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then 25 | dokku_log_fail "Invalid key specified, valid keys include: nomad-addr" 26 | fi 27 | 28 | if [[ -n "$VALUE" ]]; then 29 | dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" 30 | fn-plugin-property-write "scheduler-nomad" "$APP" "$KEY" "$VALUE" 31 | else 32 | dokku_log_info2_quiet "Unsetting ${KEY}" 33 | fn-plugin-property-delete "scheduler-nomad" "$APP" "$KEY" 34 | fi 35 | } 36 | 37 | scheduler-nomad-set-cmd "$@" 38 | -------------------------------------------------------------------------------- /templates/background.hcl.sigil: -------------------------------------------------------------------------------- 1 | job "{{ $.APP }}-{{ $.PROCESS_TYPE }}" { 2 | datacenters = ["{{ $.DATACENTER }}"] 3 | type = "service" 4 | 5 | meta { 6 | "dokku_app" = "{{ $.APP }}" 7 | "dokku_process_type" = "{{ $.PROCESS_TYPE }}" 8 | "dokku_deployment_id" = "{{ $.DEPLOYMENT_ID }}" 9 | "dokku_commit_full" = "{{ $.GIT_REV }}" 10 | } 11 | 12 | update { 13 | auto_revert = true 14 | canary = 0 15 | health_check = "task_states" 16 | healthy_deadline = "5m" 17 | max_parallel = 1 18 | min_healthy_time = "10s" 19 | progress_deadline = "10m" 20 | stagger = "30s" 21 | } 22 | 23 | migrate { 24 | health_check = "task_states" 25 | healthy_deadline = "5m" 26 | max_parallel = 1 27 | min_healthy_time = "10s" 28 | } 29 | 30 | group "{{ $.APP }}-{{ $.PROCESS_TYPE }}" { 31 | count = {{ $.PROCESS_COUNT }} 32 | 33 | ephemeral_disk { 34 | size = 300 35 | } 36 | 37 | meta = {} 38 | 39 | restart { 40 | attempts = 5 41 | delay = "15s" 42 | interval = "1m" 43 | mode = "fail" 44 | } 45 | 46 | task "{{ $.APP }}-{{ $.PROCESS_TYPE }}" { 47 | driver = "docker" 48 | leader = true 49 | kill_timeout = "{{ $.KILL_TIMEOUT }}s" 50 | shutdown_delay = "{{ $.SHUTDOWN_DELAY }}s" 51 | 52 | config { 53 | image = "{{ $.IMAGE }}" 54 | command = "/start" 55 | 56 | args = [ 57 | "{{ $.PROCESS_TYPE }}", 58 | ] 59 | 60 | labels = { 61 | "com.dokku.project.name" = "{{ $.APP }}" 62 | "com.dokku.project.version" = "{{ $.DEPLOYMENT_ID }}" 63 | "com.dokku.project.process-type" = "{{ $.PROCESS_TYPE }}" 64 | "nomad.job.name" = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 65 | "nomad.job.task_group_name" = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 66 | "nomad.job.task_name" = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 67 | } 68 | 69 | network_mode = "host" 70 | } 71 | 72 | env { 73 | APP = "{{ $.APP }}" 74 | LOG_PATH = "${NOMAD_ALLOC_DIR}/logs" 75 | {{ $.ENVIRONMENT_VARIABLES }} 76 | } 77 | 78 | resources { 79 | cpu = {{ $.RESOURCE_CPU }} 80 | memory = {{ $.RESOURCE_MEMORY }} 81 | 82 | network { 83 | mbits = {{ $.RESOURCE_NETWORK_MBITS }} 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /templates/web.hcl.sigil: -------------------------------------------------------------------------------- 1 | job "{{ $.APP }}-{{ $.PROCESS_TYPE }}" { 2 | datacenters = ["{{ $.DATACENTER }}"] 3 | type = "service" 4 | 5 | meta { 6 | "dokku_app" = "{{ $.APP }}" 7 | "dokku_process_type" = "{{ $.PROCESS_TYPE }}" 8 | "dokku_deployment_id" = "{{ $.DEPLOYMENT_ID }}" 9 | "dokku_commit_full" = "{{ $.GIT_REV }}" 10 | } 11 | 12 | update { 13 | auto_revert = true 14 | canary = 0 15 | health_check = "checks" 16 | healthy_deadline = "5m" 17 | max_parallel = 1 18 | min_healthy_time = "10s" 19 | progress_deadline = "10m" 20 | stagger = "30s" 21 | } 22 | 23 | migrate { 24 | health_check = "checks" 25 | healthy_deadline = "5m" 26 | max_parallel = 1 27 | min_healthy_time = "10s" 28 | } 29 | 30 | group "{{ $.APP }}-{{ $.PROCESS_TYPE }}" { 31 | count = {{ $.PROCESS_COUNT }} 32 | 33 | ephemeral_disk { 34 | size = 300 35 | } 36 | 37 | meta = {} 38 | 39 | restart { 40 | attempts = 5 41 | delay = "15s" 42 | interval = "3m" 43 | mode = "fail" 44 | } 45 | 46 | task "{{ $.APP }}-{{ $.PROCESS_TYPE }}" { 47 | driver = "docker" 48 | leader = true 49 | kill_timeout = "{{ $.KILL_TIMEOUT }}s" 50 | shutdown_delay = "{{ $.SHUTDOWN_DELAY }}s" 51 | 52 | config { 53 | image = "{{ $.IMAGE }}" 54 | command = "/start" 55 | 56 | args = [ 57 | "{{ $.PROCESS_TYPE }}", 58 | ] 59 | 60 | labels = { 61 | "com.dokku.project.name" = "{{ $.APP }}" 62 | "com.dokku.project.version" = "{{ $.DEPLOYMENT_ID }}" 63 | "com.dokku.project.process-type" = "{{ $.PROCESS_TYPE }}" 64 | "nomad.job.name" = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 65 | "nomad.job.task_group_name" = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 66 | "nomad.job.task_name" = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 67 | } 68 | 69 | network_mode = "host" 70 | } 71 | 72 | env { 73 | APP = "{{ $.APP }}" 74 | LOG_PATH = "{NOMAD_ALLOC_DIR}/logs" 75 | PORT = "${NOMAD_PORT_http}" 76 | {{ $.ENVIRONMENT_VARIABLES }} 77 | } 78 | 79 | resources { 80 | cpu = {{ $.RESOURCE_CPU }} 81 | memory = {{ $.RESOURCE_MEMORY }} 82 | 83 | network { 84 | mbits = {{ $.RESOURCE_NETWORK_MBITS }} 85 | port "http"{} 86 | } 87 | } 88 | 89 | service { 90 | name = "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 91 | tags = ["dokku"] 92 | port = "http" 93 | 94 | check { 95 | initial_status = "critical" 96 | name = "http" 97 | port = "http" 98 | type = "http" 99 | interval = "{{ $.CHECK_INTERVAL }}s" 100 | timeout = "{{ $.CHECK_TIMEOUT }}s" 101 | path = "{{ $.CHECK_PATH }}" 102 | 103 | check_restart { 104 | limit = 10 105 | grace = "120s" 106 | ignore_warnings = false 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------