Skip to content

Commit 9e22fde

Browse files
committed
Add an Ansible playbook for creating a droplet
1 parent 06f901b commit 9e22fde

File tree

3 files changed

+148
-48
lines changed

3 files changed

+148
-48
lines changed

README.md

Lines changed: 22 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ prerequisites:
3838

3939
* Create a DigitalOcean API token, and pass it to the inventory generator by
4040
setting the `DO_API_TOKEN` environment variable.
41+
* If you are creating a new droplet, and want to configure DNS as well, then
42+
create a CloudFlare API token, and pass it to the Ansible playbook by setting
43+
the `CLOUDFLARE_TOKEN` environment variable.
4144
* Set the vault decryption password of the Ansible vaulted file with our
4245
secrets. This may be done by setting the `ANSIBLE_VAULT_PASSWORD_FILE`
4346
environment variable to point to a file containing the password.
@@ -99,9 +102,11 @@ Naming
99102
We follow a simplified version of the naming scheme on [this blog
100103
post](https://mnx.io/blog/a-proper-server-naming-scheme/):
101104

102-
* Servers are named `<prefix>.matplotlib.org` in A records.
103-
* Servers get a functional CNAME alias (e.g., `web01.matplotlib.org`).
104-
* matplotlib.org is a CNAME to the functional CNAME of a server.
105+
* Servers are named `<prefix>.matplotlib.org` in A records, pointing to the
106+
IPv4 address of the droplet.
107+
* Servers get a functional CNAME alias (e.g., `web01.matplotlib.org`) pointing
108+
to the hostname `<prefix>.matplotlib.org`.
109+
* matplotlib.org is a CNAME alias of the functional CNAME of a server.
105110

106111
We use [planets in our Solar System](https://namingschemes.com/Solar_System)
107112
for the name prefix. When creating a new server, pick the next one in the list.
@@ -113,50 +118,27 @@ The summary of the initial setup is:
113118

114119
1. Create the droplet with monitoring and relevant SSH keys.
115120
2. Assign new droplet to the matplotlib.org project and the Web firewall.
116-
3. Grab the SSH host fingerprints.
117-
4. Reboot.
121+
3. Add DNS entries pointing to the server on CloudFlare.
122+
4. Grab the SSH host fingerprints.
123+
5. Reboot.
118124

119-
We currently use a simple $10 droplet from DigitalOcean. You can create one
120-
from the control panel, or using the `doctl` utility. Be sure to enable
121-
monitoring, and add the `website` tag and relevant SSH keys to the droplet. An
122-
example of using `doctl` is the following:
125+
We currently use a simple $12 droplet from DigitalOcean. You can create one
126+
from the control panel, or using the `create.yml` Ansible playbook:
123127

124128
```
125-
doctl compute droplet create \
126-
--image fedora-35-x64 \
127-
--region tor1 \
128-
--size s-1vcpu-2gb \
129-
--ssh-keys <key-id>,<key-id> \
130-
--tag-name website \
131-
--enable-monitoring \
132-
venus.matplotlib.org
129+
ansible-playbook create.yml
133130
```
134131

135-
Note, you will have to use `doctl compute ssh-key list` to get the IDs of the
136-
relevant SSH keys saved on DigitalOcean, and substitute them above. Save the ID
137-
of the new droplet from the output, e.g., in:
132+
This playbook will prompt you for 3 items:
138133

139-
```
140-
ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image VPC UUID Status Tags Features Volumes
141-
294098687 mpl.org 2048 1 50 tor1 Fedora 35 x64 new website monitoring,droplet_agent
142-
```
143-
144-
the droplet ID is 294098687.
134+
1. The host name of the droplet, which should follow the naming convention
135+
above.
136+
2. The functional CNAME alias of the droplet.
137+
3. The names of SSH keys to add to the droplet.
145138

146-
147-
You should also assign the new droplet to the `matplotlib.org` project and the
148-
`Web` firewall:
149-
150-
```
151-
doctl projects list
152-
# Get ID of the matplotlib.org project from the output.
153-
doctl projects resources assign <project-id> --resource=do:droplet:<droplet-id>
154-
155-
156-
doctl compute firewall list
157-
# Get ID of the Web firewall from the output.
158-
doctl compute firewall add-droplets <firewall-id> --droplet-ids <droplet-id>
159-
```
139+
Then it will create the server, as well as add DNS records on CloudFlare. Note,
140+
you must set `DO_API_TOKEN` and `CLOUDFLARE_TOKEN` in the environment to access
141+
these services.
160142

161143
Then, to ensure you are connecting to the expected server, you should grab the
162144
SSH host keys via the DigitalOcean Droplet Console:
@@ -181,14 +163,6 @@ Finally, you should reboot the droplet. This is due to a bug in cloud-init on
181163
DigitalOcean, which generates a new machine ID after startup, causing system
182164
logs to be seem invisible.
183165

184-
DNS setup
185-
---------
186-
187-
1. Add an A record for `<prefix>.matplotlib.org` to the IPv4 address of the new
188-
droplet.
189-
2. Add a CNAME record for `webNN.matplotlib.org` pointing to the given
190-
`<prefix.matplotlib.org>`.
191-
192166
Running Ansible
193167
---------------
194168

collections/requirements.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
collections:
33
- name: ansible.posix
44
- name: community.general
5+
version: ">=2.0.0"
56
- name: community.digitalocean

create.yml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
- hosts: localhost
3+
tasks:
4+
- name: Gather information about DigitalOcean droplets
5+
community.digitalocean.digital_ocean_droplet_info:
6+
register: do_droplets
7+
- name: Gather information about DigitalOcean SSH keys
8+
community.digitalocean.digital_ocean_sshkey_info:
9+
register: do_ssh_keys
10+
11+
- name: Print info on existing droplets
12+
ansible.builtin.debug:
13+
msg: >-
14+
{{ item.name }}:
15+
{{ item.networks.v4 | map(attribute='ip_address') | join(',') }}
16+
loop: "{{ do_droplets.data }}"
17+
loop_control:
18+
label: "{{ item.id }}"
19+
20+
- name: "Enter name for new droplet (subdomain only)"
21+
ansible.builtin.pause:
22+
register: input_name
23+
24+
- name: "Enter functional name for new droplet (webNN)"
25+
ansible.builtin.pause:
26+
register: input_functional
27+
28+
- name: Print available SSH public keys
29+
ansible.builtin.debug:
30+
msg: "{{ item.name}} {{ item.fingerprint }}"
31+
loop: "{{ do_ssh_keys.data }}"
32+
loop_control:
33+
label: "{{ item.id }}"
34+
35+
- name: "Enter SSH key names for new droplet (space separated)"
36+
ansible.builtin.pause:
37+
register: input_ssh_keys
38+
39+
- name: Set droplet facts
40+
ansible.builtin.set_fact:
41+
host: "{{ input_name.user_input | trim }}"
42+
functional: "{{ input_functional.user_input | trim }}"
43+
ssh_keys: >-
44+
{{
45+
do_ssh_keys.data |
46+
selectattr('name', 'in',
47+
input_ssh_keys.user_input | split | map('trim')) |
48+
map(attribute='fingerprint')
49+
}}
50+
51+
- name: Verify droplet configuration
52+
ansible.builtin.assert:
53+
that:
54+
- host in valid_planets
55+
# Must not be an existing name.
56+
- >-
57+
do_droplets.data |
58+
selectattr('name', 'equalto', '{{ host }}.matplotlib.org') |
59+
count == 0
60+
# TODO: Also check that functional name doesn't already exist.
61+
- functional is regex('^web[0-9][0-9]$')
62+
# At least 1 key, and same number as requested.
63+
- ssh_keys | length >= 1
64+
- ssh_keys | length == input_ssh_keys.user_input | split | length
65+
66+
- name: Print configuration
67+
ansible.builtin.debug:
68+
msg: "Creating droplet '{{ host }}' with SSH keys {{ ssh_keys }}"
69+
70+
- name: Please verify the above configuration
71+
ansible.builtin.pause:
72+
73+
- name: Create droplet on DigitalOcean
74+
community.digitalocean.digital_ocean_droplet:
75+
state: present
76+
name: "{{ host }}.matplotlib.org"
77+
firewall:
78+
- Web
79+
image: fedora-39-x64
80+
monitoring: true
81+
project: matplotlib.org
82+
region: tor1
83+
size: s-1vcpu-2gb
84+
ssh_keys: "{{ ssh_keys }}"
85+
tags:
86+
- website
87+
unique_name: true
88+
register: new_droplet
89+
90+
- name: Setup DNS for droplet on CloudFlare
91+
community.general.cloudflare_dns:
92+
state: present
93+
proxied: true
94+
record: "{{ host }}"
95+
type: A
96+
value: >-
97+
{{
98+
new_droplet.data.droplet.networks.v4 |
99+
selectattr('type', 'equalto', 'public') |
100+
map(attribute='ip_address') |
101+
first
102+
}}
103+
zone: matplotlib.org
104+
105+
- name: Setup functional DNS for droplet on CloudFlare
106+
community.general.cloudflare_dns:
107+
state: present
108+
proxied: true
109+
record: "{{ functional }}"
110+
type: CNAME
111+
value: "{{ host }}.matplotlib.org"
112+
zone: matplotlib.org
113+
114+
vars:
115+
# We currently name servers based on planets in the Solar System.
116+
valid_planets:
117+
- mercury
118+
- venus
119+
- earth
120+
- mars
121+
- jupiter
122+
- saturn
123+
- uranus
124+
- neptune
125+
- pluto

0 commit comments

Comments
 (0)