Digital ocean setup (#314)
- Ansible playbook for deploying on DigitalOcean, configuring space, k8s cluster, mongodb, domain / subdomain, signing subdomain, container registry, and cors - Generates helm chat in ./deploys/ directory for future use with helm directly - Initial support for deletion of created resources as well. - add documentation on how to use playbook default helm values: update to latest authsign, set default timeout to 120 seconds
This commit is contained in:
parent
1ef9f7df6d
commit
6833c9d676
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@
|
|||||||
**/config.yaml
|
**/config.yaml
|
||||||
**/signing.yaml
|
**/signing.yaml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
# digital ocean custom values
|
||||||
|
private.yml
|
||||||
|
@ -11,6 +11,8 @@ ansible-lint = "==6.4.0"
|
|||||||
yamllint = "==1.27.1"
|
yamllint = "==1.27.1"
|
||||||
ansible-core = "==2.13"
|
ansible-core = "==2.13"
|
||||||
docker = "*"
|
docker = "*"
|
||||||
|
boto3 = "*"
|
||||||
|
jmespath = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
48
ansible/README.md
Normal file
48
ansible/README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
### Playbooks to install browsertrix
|
||||||
|
|
||||||
|
#### DigitalOcean
|
||||||
|
|
||||||
|
To install browsertrix on [DigitalOcean](playbooks/do_setup.yml) you will need to the following:
|
||||||
|
|
||||||
|
* Install [ansible](https://www.ansible.com)
|
||||||
|
* Set up a DigitalOcean API token and save it in your environment as `DO_API_TOKEN`
|
||||||
|
* Set up a Spaces ACCESS and SECRET KEY and save them in your environment as `DO_AWS_ACCESS_KEY` and `DO_AWS_SECRET_KEY`
|
||||||
|
* make a copy of [group_vars/do/private.yml.example](group_vars/do/private.yml.example) to [group_vars/do/private.yml](group_vars/do/private.yml)
|
||||||
|
|
||||||
|
|
||||||
|
##### Digital Ocean Variables
|
||||||
|
|
||||||
|
See Known Issues below.
|
||||||
|
|
||||||
|
The first running of the playbook will place variables under your tmp directory in the following format YYYY-MM-DD@:HH:MMd_ocean*. Content of these files will need to be added to the [group_vars/do/private.yml](group_vars/do/private.yml) or else run as an `-e` extra value as shown below
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
-e btrix_db_url: (contents of /tmp/YYYY-MM-DD@:HH:MMd_ocean_btrix_db_url.txt`
|
||||||
|
-e lb_uuid: (contents of /tmp/YYYY-MM-DD@:HH:MMd_ocean_lb_uuid.txt`
|
||||||
|
-e loadbalancer_ip: (contents of /tmp/YYYY-MM-DD@:HH:MMd_ocean_loadbalancer_ip.txt`
|
||||||
|
-e domain_name: <your registered domain
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition change the name (it will default to demo otherwise) and the region DigitalOcean preferred region (it will default to sfo3).
|
||||||
|
|
||||||
|
##### Example Playbooks
|
||||||
|
|
||||||
|
The playbook will install the Kubernetes [package manager](https://helm.sh/) and the [DigitalOcean Controller](https://docs.digitalocean.com/reference/doctl/) both are useful in managing your installation.
|
||||||
|
|
||||||
|
* Run the playbook two times.
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
ansible-playbook -v playbooks/do_setup.yml
|
||||||
|
ansible-playbook -v playbooks/do_setup.yml -t helm_upgrade -e btrix_db_url= -e lb_uuid= -e loadbalancer_ip=
|
||||||
|
```
|
||||||
|
|
||||||
|
Every subsequent time one needs to run helm updates the `-t helm_upgrade` can be passed to the playbook like so:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
ansible-playbook -v playbooks/do_setup.yml -t helm_upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Known Issues:
|
||||||
|
|
||||||
|
The `doctl` tool is the only one that allows us to create a mongodb password. We continue to investigate why this cannot use ansible's [set_fact](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html) in the playbook.
|
||||||
|
The Kubernetes task creates a loadbalancer which will not be ready by the time the playbook completes the first time. So a second or sometimes 3rd run will be needed.
|
2
ansible/deploys/.gitignore
vendored
Normal file
2
ansible/deploys/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
70
ansible/group_vars/do/do-values.template.yaml
Normal file
70
ansible/group_vars/do/do-values.template.yaml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Resources / Node Types
|
||||||
|
main_node_type: main
|
||||||
|
|
||||||
|
crawler_node_type: crawling
|
||||||
|
redis_node_type: crawling
|
||||||
|
|
||||||
|
crawler_requests_cpu: "1200m"
|
||||||
|
crawler_limits_cpu: "2200m"
|
||||||
|
crawler_requests_memory: "1200Mi"
|
||||||
|
crawler_limits_memory: "3200Mi"
|
||||||
|
|
||||||
|
# Registry
|
||||||
|
{% if use_do_registry %}
|
||||||
|
api_image: "{{ registry_endpoint }}/webrecorder/browsertrix-backend:{{ image_tag }}"
|
||||||
|
nginx_image: "{{ registry_endpoint }}/webrecorder/browsertrix-frontend:{{ image_tag }}"
|
||||||
|
crawler_image: "{{ registry_endpoint }}/webrecorder/browsertrix-crawler:{{ image_tag }}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# DB
|
||||||
|
mongo_local: false
|
||||||
|
|
||||||
|
mongo_auth:
|
||||||
|
db_url: "{{ db_url }}"
|
||||||
|
|
||||||
|
# Storage
|
||||||
|
minio_local: false
|
||||||
|
|
||||||
|
storages:
|
||||||
|
- name: "default"
|
||||||
|
access_key: "{{ lookup('env', 'DO_AWS_ACCESS_KEY')}}"
|
||||||
|
secret_key: "{{ lookup('env', 'DO_AWS_SECRET_KEY')}}"
|
||||||
|
|
||||||
|
endpoint_url: "{{ bucket_endpoint_url }}"
|
||||||
|
|
||||||
|
|
||||||
|
# Domain
|
||||||
|
ingress:
|
||||||
|
host: {{ full_domain }}
|
||||||
|
cert_email: {{ cert_email }}
|
||||||
|
scheme: "https"
|
||||||
|
tls: true
|
||||||
|
|
||||||
|
|
||||||
|
# Signing
|
||||||
|
{% if enable_signing %}
|
||||||
|
signer:
|
||||||
|
enabled: true
|
||||||
|
host: {{ full_signing_domain }}
|
||||||
|
cert_email: {{ cert_email }}
|
||||||
|
image_pull_policy: "IfNotPresent"
|
||||||
|
auth_token: {{ signing_authtoken }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
# User Auth
|
||||||
|
superuser:
|
||||||
|
email: {{ superuser_email }}
|
||||||
|
password: {{ superuser_password }}
|
||||||
|
|
||||||
|
registration_enabled: "{{ '1' if registration_enabled else '0' }}"
|
||||||
|
|
||||||
|
|
||||||
|
# Admin Send Email Options
|
||||||
|
email:
|
||||||
|
smtp_port: {{ smtp_port }}
|
||||||
|
smtp_host: {{ smtp_host }}
|
||||||
|
sender_email: {{ sender_email }}
|
||||||
|
password: {{ sender_password }}
|
||||||
|
|
||||||
|
|
39
ansible/group_vars/do/main.yml
Normal file
39
ansible/group_vars/do/main.yml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
project_name: "default"
|
||||||
|
|
||||||
|
main_node_size: "s-1vcpu-2gb"
|
||||||
|
crawl_node_size: "c-4"
|
||||||
|
droplet_region: "sfo3"
|
||||||
|
|
||||||
|
node_pools:
|
||||||
|
- name=main-app;size={{ main_node_size }};label=nodeType=main;count=2
|
||||||
|
- name=crawling;size={{ crawl_node_size }};label=nodeType=crawling;taint=nodeType=crawling:NoSchedule;auto-scale=true;min-nodes=1;max-nodes=3;count=1
|
||||||
|
|
||||||
|
db_name: "{{ project_name }}"
|
||||||
|
k8s_name: "{{ project_name }}"
|
||||||
|
|
||||||
|
bucket_name: "{{ project_name }}"
|
||||||
|
bucket_path: "crawls"
|
||||||
|
|
||||||
|
domain: "browsertrix.cloud"
|
||||||
|
subdomain: "{{ project_name }}"
|
||||||
|
|
||||||
|
use_do_registry: true
|
||||||
|
image_tag: "dev"
|
||||||
|
|
||||||
|
enable_signing: true
|
||||||
|
signing_host: "signing"
|
||||||
|
|
||||||
|
superuser_email: "dev@webrecorder.net"
|
||||||
|
superuser_password: "PassW0rd!"
|
||||||
|
|
||||||
|
registration_enabled: true
|
||||||
|
|
||||||
|
cert_email: "{{ superuser_email }}"
|
||||||
|
|
||||||
|
smtp_port: ""
|
||||||
|
smtp_host: ""
|
||||||
|
sender_email: ""
|
||||||
|
sender_password: ""
|
||||||
|
|
||||||
|
|
206
ansible/playbooks/do_setup.yml
Normal file
206
ansible/playbooks/do_setup.yml
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
---
|
||||||
|
- name: deploy browsertrix cloud on digital ocean
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
vars_files:
|
||||||
|
- ../group_vars/do/main.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Init
|
||||||
|
- name: d_ocean | init | install doctl and helm
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
loop:
|
||||||
|
- helm
|
||||||
|
- doctl
|
||||||
|
|
||||||
|
- name: d_ocean | init | set full domain
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
full_domain: "{{ subdomain + '.' + domain if subdomain else domain }}"
|
||||||
|
|
||||||
|
# MongoDB
|
||||||
|
# ===========================================
|
||||||
|
- name: d_ocean | db | test for existing mongodb
|
||||||
|
ansible.builtin.command: doctl db list
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
register: db_check
|
||||||
|
|
||||||
|
- name: d_ocean | db | create mongodb database
|
||||||
|
ansible.builtin.command: doctl databases create {{ db_name }} --region {{ droplet_region }} --engine mongodb --version 5 --output json
|
||||||
|
async: 1800
|
||||||
|
poll: 60
|
||||||
|
register: db_create_result
|
||||||
|
when: db_check.stdout.find(db_name) == -1
|
||||||
|
|
||||||
|
- name: d_ocean | db | set db url
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
db_url: "{{ db_create_result.stdout | from_json | json_query('[0].private_connection.uri') }}"
|
||||||
|
|
||||||
|
- name: d_ocean | db | set db id
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
db_uuid: db_create_result.stdout | from_json | json_query('[0].id')
|
||||||
|
|
||||||
|
# Storage (Space)
|
||||||
|
# ===========================================
|
||||||
|
- name: d_ocean | space | create new
|
||||||
|
community.digitalocean.digital_ocean_spaces:
|
||||||
|
name: "{{ bucket_name }}"
|
||||||
|
state: present
|
||||||
|
oauth_token: "{{ lookup('env', 'DO_API_TOKEN') }}"
|
||||||
|
aws_access_key_id: "{{ lookup('env', 'DO_AWS_ACCESS_KEY') }}"
|
||||||
|
aws_secret_access_key: "{{ lookup('env', 'DO_AWS_SECRET_KEY') }}"
|
||||||
|
region: "{{ droplet_region }}"
|
||||||
|
register: db_space_result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: d_ocean | space | set endpoint urls
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
endpoint_url: "{{ db_space_result.data.space.endpoint_url }}/"
|
||||||
|
bucket_endpoint_url: "{{ db_space_result.data.space.endpoint_url }}/{{ db_space_result.data.space.name }}/{{ bucket_path }}/"
|
||||||
|
|
||||||
|
- name: d_ocean | space | set bucket cors
|
||||||
|
community.aws.aws_s3_cors:
|
||||||
|
name: "{{ bucket_name }}"
|
||||||
|
aws_access_key: "{{ lookup('env', 'DO_AWS_ACCESS_KEY') }}"
|
||||||
|
aws_secret_key: "{{ lookup('env', 'DO_AWS_SECRET_KEY') }}"
|
||||||
|
endpoint_url: "{{ endpoint_url }}"
|
||||||
|
region: "{{ droplet_region }}"
|
||||||
|
state: present
|
||||||
|
rules:
|
||||||
|
- allowed_origins:
|
||||||
|
- "https://{{ full_domain }}"
|
||||||
|
allowed_methods:
|
||||||
|
- GET
|
||||||
|
- HEAD
|
||||||
|
allowed_headers:
|
||||||
|
- "*"
|
||||||
|
expose_headers:
|
||||||
|
- Content-Range
|
||||||
|
- Content-Encoding
|
||||||
|
- Content-Length
|
||||||
|
|
||||||
|
# K8S
|
||||||
|
# ===========================================
|
||||||
|
- name: d_ocean | k8s | test for existing k8s cluster
|
||||||
|
ansible.builtin.command: doctl k8s cluster list
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
register: cluster_check
|
||||||
|
|
||||||
|
- name: d_ocean | k8s | create a kubernetes cluster in {{ droplet_region }}
|
||||||
|
# skip_ansible_lint
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
doctl kubernetes cluster create {{ k8s_name }} --1-clicks ingress-nginx,cert-manager --node-pool
|
||||||
|
"{{ node_pools|join(',') }}"
|
||||||
|
--region={{ droplet_region }}
|
||||||
|
|
||||||
|
async: 1800
|
||||||
|
poll: 60
|
||||||
|
changed_when: false
|
||||||
|
when: cluster_check.stdout.find(k8s_name) == -1
|
||||||
|
|
||||||
|
- name: d_ocean | k8s | Get information about our cluster
|
||||||
|
community.digitalocean.digital_ocean_kubernetes_info:
|
||||||
|
oauth_token: "{{ lookup('ansible.builtin.env', 'DO_API_TOKEN') }}"
|
||||||
|
name: "{{ k8s_name }}"
|
||||||
|
return_kubeconfig: true
|
||||||
|
register: my_cluster
|
||||||
|
|
||||||
|
- name: d_ocean | k8s | print information about an existing DigitalOcean Kubernetes cluster
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: Cluster name is {{ my_cluster.data.name }}, ID is {{ my_cluster.data.id }}
|
||||||
|
failed_when: not my_cluster
|
||||||
|
|
||||||
|
- name: d_ocean | k8s | save kubectl config to kube_dir
|
||||||
|
ansible.builtin.command: doctl kubernetes cluster kubeconfig save {{ my_cluster.data.id }}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: d_ocean | k8s | get loadbalancer info from doctl
|
||||||
|
ansible.builtin.command: doctl k8s cluster list-associated-resources {{ my_cluster.data.id }} --format LoadBalancers --output json
|
||||||
|
register: lb_id_result
|
||||||
|
retries: 100
|
||||||
|
delay: 5
|
||||||
|
changed_when: lb_id_result.stdout | from_json | json_query('load_balancers') | length > 0
|
||||||
|
until: lb_id_result.stdout | from_json | json_query('load_balancers') | length > 0
|
||||||
|
|
||||||
|
- name: d_ocean | k8s | parse lb id
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
lb_id: "{{ lb_id_result.stdout | from_json | json_query('load_balancers[0].id') }}"
|
||||||
|
|
||||||
|
# DNS
|
||||||
|
# ===========================================
|
||||||
|
- name: d_ocean | dns | grab loadbalancer ip using doctl
|
||||||
|
ansible.builtin.command: doctl compute load-balancer get --format IP "{{ lb_id }}"
|
||||||
|
register: loadbalancer_ip_result
|
||||||
|
retries: 100
|
||||||
|
delay: 5
|
||||||
|
changed_when: loadbalancer_ip_result.stdout_lines | length > 1
|
||||||
|
until: loadbalancer_ip_result.stdout_lines | length > 1
|
||||||
|
|
||||||
|
- name: d_ocean | dns | parse ip
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
lb_ip: "{{ loadbalancer_ip_result.stdout_lines[1] }}"
|
||||||
|
|
||||||
|
- name: d_ocean | dns | register the dns for browsertrix cloud
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
doctl compute domain records create --record-type A --record-name "{{ subdomain if subdomain else '@' }}" --record-data "{{ lb_ip }}" "{{ domain }}"
|
||||||
|
|
||||||
|
changed_when: dns_result.rc == 0
|
||||||
|
register: dns_result
|
||||||
|
|
||||||
|
|
||||||
|
# Signing + DNS
|
||||||
|
# ===========================================
|
||||||
|
- name: d_coean | signing | set signing domain + authtoken
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
full_signing_domain: "{{ signing_host }}.{{ full_domain }}"
|
||||||
|
signing_subdomain: "{{ signing_host + '.' + subdomain if subdomain else signing_host }}"
|
||||||
|
signing_authtoken: "{{ 99999999 | random | to_uuid }}"
|
||||||
|
|
||||||
|
when: enable_signing
|
||||||
|
|
||||||
|
- name: d_ocean | signing | register the dns for signing subdomain
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
doctl compute domain records create --record-type A --record-name "{{ signing_subdomain }}" --record-data "{{ lb_ip }}" "{{ domain }}"
|
||||||
|
|
||||||
|
register: signing_dns_result
|
||||||
|
when: enable_signing
|
||||||
|
|
||||||
|
|
||||||
|
# Registry
|
||||||
|
# ===========================================
|
||||||
|
- name: d_ocean | registry | get endpoint, if using registry
|
||||||
|
ansible.builtin.command: doctl registry get --format Endpoint
|
||||||
|
register: do_registry_result
|
||||||
|
when: use_do_registry
|
||||||
|
failed_when: do_registry_result.stdout_lines | length < 2
|
||||||
|
|
||||||
|
- name: d_ocean | registry | store registry endpoint
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
registry_endpoint: "{{ do_registry_result.stdout_lines[1] }}"
|
||||||
|
when: use_do_registry
|
||||||
|
|
||||||
|
- name: d_ocean | registry | add to new k8s cluster
|
||||||
|
ansible.builtin.shell: set -o pipefail && doctl registry kubernetes-manifest | kubectl apply -f -
|
||||||
|
when: use_do_registry
|
||||||
|
|
||||||
|
|
||||||
|
# Helm Output + Deploy
|
||||||
|
# ===========================================
|
||||||
|
- name: d_ocean | helm | output values yaml
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: ../group_vars/do/do-values.template.yaml
|
||||||
|
dest: ../deploys/{{ project_name }}-values.yaml
|
||||||
|
mode: u+rw
|
||||||
|
|
||||||
|
- name: d_ocean | helm | deploy btrix
|
||||||
|
ansible.builtin.command: helm upgrade --install -f ../../chart/values.yaml -f ../deploys/{{ project_name }}-values.yaml btrix ../../chart/
|
||||||
|
register: helm_result
|
||||||
|
changed_when: helm_result.rc == 0
|
||||||
|
|
||||||
|
tags: helm_upgrade
|
@ -12,7 +12,7 @@ volume_storage_class:
|
|||||||
# if set, set the node selector 'nodeType' to this crawling pods
|
# if set, set the node selector 'nodeType' to this crawling pods
|
||||||
# crawler_node_type:
|
# crawler_node_type:
|
||||||
|
|
||||||
registration_enabled: 0
|
registration_enabled: "0"
|
||||||
jwt_token_lifetime_minutes: 1440
|
jwt_token_lifetime_minutes: 1440
|
||||||
|
|
||||||
# number of workers for backend api
|
# number of workers for backend api
|
||||||
@ -118,7 +118,7 @@ crawler_namespace: "crawlers"
|
|||||||
crawl_retries: 1000
|
crawl_retries: 1000
|
||||||
|
|
||||||
# browsertrix-crawler args:
|
# browsertrix-crawler args:
|
||||||
crawler_args: "--timeout 90 --logging stats,behaviors,debug --generateWACZ --text --workers 4 --collection thecrawl --screencastPort 9037 --sizeLimit 100000000000 --timeLimit 18000 --healthCheckPort 6065 --waitOnDone"
|
crawler_args: "--timeout 120 --logging stats,behaviors,debug --generateWACZ --text --workers 4 --collection thecrawl --screencastPort 9037 --sizeLimit 100000000000 --timeLimit 18000 --healthCheckPort 6065 --waitOnDone --behaviorTimeout 300"
|
||||||
|
|
||||||
crawler_requests_cpu: "800m"
|
crawler_requests_cpu: "800m"
|
||||||
crawler_limits_cpu: "1200m"
|
crawler_limits_cpu: "1200m"
|
||||||
@ -199,9 +199,9 @@ ingress:
|
|||||||
# optionally enable signer
|
# optionally enable signer
|
||||||
signer:
|
signer:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
image: webrecorder/authsign:0.5.0
|
||||||
# host: <set to signer domain>
|
# host: <set to signer domain>
|
||||||
# cert_email: "test@example.com
|
# cert_email: "test@example.com
|
||||||
# image: webrecorder/authsign:0.4.0
|
|
||||||
# image_pull_policy: "IfNotPresent"
|
# image_pull_policy: "IfNotPresent"
|
||||||
# auth_token: <set to custom value>
|
# auth_token: <set to custom value>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user