https://github.com/cogini/ansible-role-elixir-release
Ansible role to deploy an Elixir/Phoenix release using systemd
https://github.com/cogini/ansible-role-elixir-release
ansible ansible-role elixir systemd-unit
Last synced: 11 months ago
JSON representation
Ansible role to deploy an Elixir/Phoenix release using systemd
- Host: GitHub
- URL: https://github.com/cogini/ansible-role-elixir-release
- Owner: cogini
- License: mit
- Created: 2018-05-20T07:51:11.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2020-02-07T10:28:20.000Z (over 6 years ago)
- Last Synced: 2025-04-11T05:39:53.668Z (about 1 year ago)
- Topics: ansible, ansible-role, elixir, systemd-unit
- Language: Shell
- Homepage:
- Size: 42 KB
- Stars: 17
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# elixir-release
This Ansible role deploys Elixir/Phoenix releases.
It uses Erlang "releases" with systemd for process supervision, as described in
"[Deploying Elixir apps with Ansible](https://www.cogini.com/blog/deploying-elixir-apps-with-ansible/)" and.
"[Best practices for deploying Elixir apps](https://www.cogini.com/blog/best-practices-for-deploying-elixir-apps/)".
## Directory structure
It uses a structure like Capistrano to manage the release files. The base
directory is named for the app, e.g. `/srv/foo`, with a `releases` directory
under it. When the role deploys a release, it creates a directory named by a
timestamp, e.g. `/srv/foo/releases/20190603T072116`. It unpacks the files
under it, makes a symlink from `/srv/foo/current` to the new directory.
## Restarting
After deploying the release, it restarts the app to make it live.
By default, when `elixir_release_restart_method: systemctl`, it does this by running:
```shell
sudo /bin/systemctl restart foo
```
The deploy user account needs sufficient permissions to restart the app.
Instead of giving the deploy account full sudo permissions, a user-specific
sudo config file specifies what commands it can run, e.g. `/etc/sudoers.d/deploy-foo`:
deploy ALL=(ALL) NOPASSWD: /bin/systemctl start foo, /bin/systemctl stop foo, /bin/systemctl restart foo
Better is if we didn't require sudo permissions at all. One option is to take
advantage of systemd to restart the app.
Set `elixir_release_restart_method: systemd_flag`, the deploy process touches a
`/srv/foo/flags/restart.flag` file on the disk after deploying the code.
Systemd notices and restarts it with the new code.
See [mix-deploy-example](https://github.com/cogini/mix-deploy-example) for a full example.
# Example Playbook
A minimal playbook, for an app called `foo`:
- hosts: '*'
become: true
vars:
elixir_release_app_name: foo
roles:
- cogini.elixir-release
Put this in `ansible/playbooks/deploy-app.yml`.
First, set up the target machine, e.g. installing packages and creating directories.
Run this from your dev machine, specifying a user with sudo permissions:
ansible-playbook -u $USER -v -l web_servers playbooks/deploy-app.yml --skip-tags deploy -D
Next, deploy the code. Run this from the build server, from a user account with
ssh access to the deploy account on the target machine:
ansible-playbook -u deploy -v -l web_servers playbooks/deploy-app.yml --tags deploy --extra-vars ansible_become=false -D
A more heaviliy customized playbook:
```yaml
- hosts: '*'
become: true
vars:
elixir_release_app_name: foo
elixir_release_app_user: bar
elixir_release_deploy_user: deploy
elixir_release_mix_env: frontend
# elixir_release_release_name: "{{ elixir_release_mix_env }}"
# elixir_release_release_system: distillery
# elixir_release_start_command: foreground
elixir_release_systemd_source: mix_systemd
elixir_release_base_dir: /opt/bar
elixir_release_app_dirs:
- configuration
- runtime
- logs
- tmp
- state
- cache
elixir_release_tmp_directory_base: /var/tmp/bar
elixir_release_state_directory_base: /var/bar
elixir_release_http_listen_port: 8080
elixir_release_cache_directory_mode: 0700
elixir_release_configuration_directory_mode: 0755
elixir_release_logs_directory_mode: 0755
elixir_release_state_directory_mode: 0755
elixir_release_tmp_directory_mode: 0755
elixir_release_sudoers_file: "{{ elixir_release_app_user }}-{{ elixir_release_service_name }}"
# Location of source app, assuming that the deploy scripts are in a separate repo in a parallel dir
elixir_release_src_dir: "{{ playbook_dir }}/../../../foo"
roles:
- cogini.elixir-release
```
# Role Variables
System used to build releases, either "mix" or "distillery".
elixir_release_release_system: "mix"
Location of app to get release files. By default, it assumes that you have an `ansible` directory
in your app source
elixir_release_app_dir: "{{ role_path }}/../../.."
Erlang name of the application, used to by Distillery to name directories
and scripts.
elixir_release_app_name: my_app
Name of release, by default `app_name`, but often MIX_ENV.
elixir_release_release_name: "{{ elixir_release_app_name }}"
External name of the app, used to name the systemd service and directories.
By default, it converts underscores to dashes:
elixir_release_service_name: "{{ elixir_release_app_name | replace('_', '-') }}"
Elixir application name. By default, it is the CamelCase version of the app name:
elixir_release_app_module: "{{ elixir_release_service_name.title().replace('_', '') }}"
Version of the app to release. If not specified, will read it from the `start_erl.data`
file in the release directory.
elixir_release_version: "0.1.0"
For security, we use separate accounts to deploy the app and to run it. The
deploy account owns the code and config files, and has rights to restart the
app. We normally use a separate account called `deploy`. The app runs under a
separate account with the minimum permissions it needs. We normally create a
name matching the app, e.g. `foo` or use a generic name like `app`.
The release files are owned by `deploy:app` with mode 0644 so that the app can read them.
OS account that deploys and owns the release files:
elixir_release_deploy_user: deploy
OS group that deploys and owns the release files:
elixir_release_deploy_group: "{{ elixir_release_deploy_user }}"
OS account that the app runs under:
elixir_release_app_user: "{{ elixir_release_service_name }}"
OS group that the app runs under:
elixir_release_app_group: "{{ elixir_release_app_user }}"
App release environment, i.e. the setting of `MIX_ENV`, used to find the release file under the `_build` dir:
elixir_release_mix_env: prod
Directory prefix for release files:
elixir_release_base_dir: /srv
Base directory for deploy files:
elixir_release_deploy_dir: "{{ elixir_release_base_dir }}/{{ elixir_release_service_name }}"
Directories under deploy dir.
Where release tarballs are unpacked:
elixir_release_releases_dir: "{{ elixir_release_deploy_dir }}/releases"
Currently running release (symlink):
elixir_release_current_dir: "{{ elixir_release_deploy_dir }}/current"
Location of deploy scripts:
elixir_release_scripts_dir: "{{ elixir_release_deploy_dir }}/bin"
Flag file dir, used to signal restart:
elixir_release_flags_dir: "{{ elixir_release_deploy_dir }}/flags"
Directories where the app keeps its files, following [systemd](https://www.freedesktop.org/software/systemd/man/systemd.exec.html).
elixir_release_app_dirs:
- configuration
- runtime
# - logs
# - tmp
# - state
# - cache
Whether to use [conform](https://github.com/bitwalker/conform):
elixir_release_conform: false
elixir_release_conform_conf_path: "{{ elixir_release_configuration_dir }}/config.conform"
How we should restart the app:
elixir_release_restart_method: systemctl
# elixir_release_restart_method: systemd_flag
# elixir_release_restart_method: touch
Options are:
* `systemctl`, which runs `systemctl restart foo`
* `systemd_flag`, which touches the file `{{ elixir_release_shutdown_flags_dir }}/restart.flag`
* `touch`, which touches the file `{{ elixir_release_shutdown_flags_dir }}/restart.flag`.
Directory permissions are 0770, allowing the managed process to restart itself.
Which users are allowed to restart the app using `sudo /bin/systemctl restart` when method == `systemctl`.
elixir_release_restart_users:
- "{{ elixir_release_deploy_user }}"
Set to `[]` and nobody can restart, or add additional names, e.g. `- "{{ elixir_release_app_user }}"`.
## systemd and scripts
By default this role assumes that you are using
[mix_systemd](https://hex.pm/packages/mix_systemd) to generate the systemd unit
file and [mix_deploy](https://hex.pm/packages/mix_deploy) to generate lifecycle
scripts.
`elixir_release_systemd_source` controls the source of the systemd unit file.
elixir_release_systemd_source: mix_systemd
With the default value of `mix_systemd`, the role copies the systemd unit files from the
`_build/{{ elixir_release_mix_env }}/systemd` directory. Set it to `self`, nd
this role will generate a systemd unit file from a template.
`elixir_release_scripts_source` controls the source of the scripts.
elixir_release_scripts_source: bin
With the default value of `bin`, the role copies scripts from the project's `bin` directory
to `/srv/foo/bin` on the target system. Set it to `mix_deploy` if you have set
`output_dir_per_env: true` in the `mix_deploy` config, storing the generated scripts under `_build`.
The following variables are used when generating the systemd unit file:
Port that the app listens for HTTP connections on:
elixir_release_http_listen_port: 4000
Port that the app listens for HTTPS connections on:
elixir_release_https_listen_port: 4001
Open file limit:
elixir_release_limit_nofile: 65536
Seconds to wait between restarts:
elixir_release_systemd_restart_sec: 5
`LANG` environment var:
elixir_release_lang: "en_US.UTF-8"
umask:
elixir_release_umask: "0027"
Target systemd version, used to enable more advanced features:
elixir_release_systemd_version: 219
Systemd service type: simple | exec | notify | forking
See systemd [Type](https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=)
elixir_release_service_type: simple
Release command to execute to start app. mix uses start for simple, daemon for forking.
distilery uses foreground for simple, start for forking.
elixir_release_start_command: start
PID file when using forking service type:
elixir_release_pid_file: "{{ elixir_release_runtime_dir }}/{{ elixir_release_app_name}}.pid"
List of ExecStartPre scripts in systemd unit file:
elixir_release_exec_start_pre: []
List of environment vars to set in systemd unit file:
elixir_release_env_vars: []
# Dependencies
None
# Requirements
None
# License
MIT
# Author Information
Jake Morrison