Skip to content

Commit 16b97d8

Browse files
authored
Merge pull request #37 from QuLogic/ansible-setup
Add an Ansible playbook for creating a droplet
2 parents 06f901b + e272779 commit 16b97d8

File tree

3 files changed

+168
-45
lines changed

3 files changed

+168
-45
lines changed

README.md

+25-45
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,51 +118,34 @@ 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 settings:
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:
139+
You may also pass these directly to Ansible as:
149140

150141
```
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>
142+
ansible-playbook create.yml --extra-vars "host=pluto functional=web99 ssh_keys='a b c'"
159143
```
160144

145+
The playbook will create the server, as well as add DNS records on CloudFlare.
146+
Note, you must set `DO_API_TOKEN` and `CLOUDFLARE_TOKEN` in the environment to
147+
access these services.
148+
161149
Then, to ensure you are connecting to the expected server, you should grab the
162150
SSH host keys via the DigitalOcean Droplet Console:
163151

@@ -181,14 +169,6 @@ Finally, you should reboot the droplet. This is due to a bug in cloud-init on
181169
DigitalOcean, which generates a new machine ID after startup, causing system
182170
logs to be seem invisible.
183171

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-
192172
Running Ansible
193173
---------------
194174

collections/requirements.yml

+1
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

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

0 commit comments

Comments
 (0)