diff --git a/provision-contest/ansible/domserver.yml b/provision-contest/ansible/domserver.yml index 3c1ff1fb..f467dd5f 100644 --- a/provision-contest/ansible/domserver.yml +++ b/provision-contest/ansible/domserver.yml @@ -37,18 +37,18 @@ tags: ssh - role: mysql_server tags: mysql_server - - role: domjudge_checkout - tags: domjudge_checkout - - role: domjudge_build - tags: domjudge_build - - role: domserver - tags: domserver - role: mysql_replication tags: mysql_replication when: REPLICATION_PASSWORD is defined - role: keepalived tags: keepalived when: KEEPALIVED_PRIORITY is defined + - role: domjudge_checkout + tags: domjudge_checkout + - role: domjudge_build + tags: domjudge_build + - role: domserver + tags: domserver - role: prometheus_target_web tags: prometheus_target_web vars: diff --git a/provision-contest/ansible/group_vars/all/all.yml.example b/provision-contest/ansible/group_vars/all/all.yml.example index b2e08b3d..fcea1e81 100644 --- a/provision-contest/ansible/group_vars/all/all.yml.example +++ b/provision-contest/ansible/group_vars/all/all.yml.example @@ -52,6 +52,10 @@ HOSTS: domjudge-laptop: 10.3.3.200 pc2: 10.3.3.241 +# Set this to true to enable load balancing instead of (automatic) failover +# DBA_PASSWORD needs to be set for this in secret.yml +DOMSERVER_LOADBALANCING: false + TIMEZONE: "Asia/Dhaka" PHP_FPM_MAX_CHILDREN: 400 diff --git a/provision-contest/ansible/group_vars/all/secret.yml.example b/provision-contest/ansible/group_vars/all/secret.yml.example index 2b1a0932..bd06e2bd 100644 --- a/provision-contest/ansible/group_vars/all/secret.yml.example +++ b/provision-contest/ansible/group_vars/all/secret.yml.example @@ -8,6 +8,10 @@ # Set this to enable master-master replication between two domservers. #REPLICATION_PASSWORD: {some-strong-replication-password} +# Database administrator password. Needed when DOMSERVER_LOADBALANCING is true +# Note: this user will have access from the whole SERVER_IP_PREFIX range +#DBA_PASSWORD: some-dba-password + # Database user password. DB_PASSWORD: {some-strong-database-password} diff --git a/provision-contest/ansible/roles/domserver/tasks/main.yml b/provision-contest/ansible/roles/domserver/tasks/main.yml index 57415436..6add053a 100644 --- a/provision-contest/ansible/roles/domserver/tasks/main.yml +++ b/provision-contest/ansible/roles/domserver/tasks/main.yml @@ -19,17 +19,27 @@ owner: domjudge notify: Fix permissions on domjudge inplace-install +- name: set the DBA credentials + set_fact: + dba_credentials: | + {% if host_type == 'domserver' and DBA_PASSWORD is defined %} + -u domjudge_dba -p {{ DBA_PASSWORD }} + {% else %} + -s -u root + {% endif %} + # When using replication, the DB will be dropped and recreated on the slave later. -- name: Check if the database is configured - command: "{{ DJ_DIR }}/bin/dj_setup_database -s -u root status" +- name: check if the database is configured + command: "{{ DJ_DIR }}/bin/dj_setup_database {{ dba_credentials }} status" register: db_status ignore_errors: true changed_when: false check_mode: no + when: not DOMSERVER_LOADBALANCING or groups['domserver'][0] == inventory_hostname or host_type != 'domserver' -- name: Make sure the database is configured - command: "{{ DJ_DIR }}/bin/dj_setup_database -s -u root bare-install" - when: "'failed' in db_status.stdout" +- name: make sure the database is configured + command: "{{ DJ_DIR }}/bin/dj_setup_database {{ dba_credentials }} bare-install" + when: "(not DOMSERVER_LOADBALANCING or groups['domserver'][0] == inventory_hostname or host_type != 'domserver') and 'failed' in db_status.stdout" - name: Install required packages apt: diff --git a/provision-contest/ansible/roles/domserver/templates/dbpasswords.secret.j2 b/provision-contest/ansible/roles/domserver/templates/dbpasswords.secret.j2 index 15706d84..0b8b5865 100644 --- a/provision-contest/ansible/roles/domserver/templates/dbpasswords.secret.j2 +++ b/provision-contest/ansible/roles/domserver/templates/dbpasswords.secret.j2 @@ -1,3 +1,7 @@ # {{ansible_managed}} # Format: 'unused:::::' +{% if host_type == 'domserver' and DOMSERVER_LOADBALANCING %} +unused:{{DOMSERVER_IP}}:domjudge:domjudge:{{DB_PASSWORD}}:3306 +{% else %} unused:localhost:domjudge:domjudge:{{DB_PASSWORD}}:3306 +{% endif %} diff --git a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 index 7e0d1904..1756013f 100644 --- a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 +++ b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 @@ -11,6 +11,11 @@ set $domjudgeRoot {{ DJ_DIR }}/webapp/public; set $prefix ''; location / { +{% if host_type == 'domserver' and DOMSERVER_LOADBALANCING %} + if ($access_allowed = false) { + return 403; + } +{% endif %} root $domjudgeRoot; try_files $uri @domjudgeFront; diff --git a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 index 3e005e7b..76577501 100644 --- a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 +++ b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 @@ -7,6 +7,43 @@ upstream domjudge { server unix:/var/run/php-fpm-domjudge.sock; # if using with etc/domjudge-fpm.conf } +{% if host_type == 'domserver' and DOMSERVER_LOADBALANCING %} +upstream domjudge-loadbalanced { + least_conn; + keepalive 100; +{% for host in groups['domserver'] %} + server {{ hostvars[host].ansible_host }}:444; +{% endfor %} +} + +server { + listen 444 ssl http2; + listen [::]:444 ssl http2; + server_name _default_; + + ssl_certificate {{DOMSERVER_SSL_CERT}}; + ssl_certificate_key {{DOMSERVER_SSL_KEY}}; + ssl_session_timeout 5m; + ssl_prefer_server_ciphers on; + + add_header Strict-Transport-Security max-age=31556952; + include /etc/nginx/snippets/domjudge-inner; + + set_real_ip_from {{ SERVER_IP_PREFIX }}.0/24; + real_ip_header X-Forwarded-For; + real_ip_recursive on; +} + +map $realip_remote_addr $access_allowed { + default false; + {{ DOMSERVER_IP }} true; +{% for host in groups['domserver'] %} + {{ hostvars[host].ansible_host }} true; +{% endfor %} +} + +{% endif %} + server { listen 80 default; server_name _default_; @@ -24,5 +61,19 @@ server { add_header Strict-Transport-Security max-age=31556952; send_timeout 36000s; +{% if host_type == 'domserver' and DOMSERVER_LOADBALANCING %} + location / { + proxy_pass https://domjudge-loadbalanced; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_request_buffering off; + proxy_buffering off; + } +{% else %} include /etc/nginx/snippets/domjudge-inner; +{% endif %} } diff --git a/provision-contest/ansible/roles/mysql_server/tasks/main.yml b/provision-contest/ansible/roles/mysql_server/tasks/main.yml index 0e9a6a9a..a2477398 100644 --- a/provision-contest/ansible/roles/mysql_server/tasks/main.yml +++ b/provision-contest/ansible/roles/mysql_server/tasks/main.yml @@ -72,3 +72,25 @@ loop: - load-db - dump-db + +- name: create mysql user for for DOMjudge database administration + mysql_user: + name: domjudge_dba + host: '{{ item }}' + password: "{{ DBA_PASSWORD }}" + append_privs: true + priv: 'domjudge.*:ALL,GRANT/*.*:CREATE USER,RELOAD' + state: present + when: host_type == 'domserver' and DBA_PASSWORD is defined + loop: "{{ groups['domserver'] | map('extract', hostvars, 'ansible_host') + [DOMSERVER_IP] }}" + +- name: create mysql user for for DOMjudge when we are doing loadbalancing + mysql_user: + name: domjudge + host: '{{ item }}' + password: "{{ DB_PASSWORD }}" + append_privs: true + priv: 'domjudge.*:SELECT,INSERT,UPDATE,DELETE' + state: present + when: host_type == 'domserver' and DOMSERVER_LOADBALANCING + loop: "{{ groups['domserver'] | map('extract', hostvars, 'ansible_host') + [DOMSERVER_IP] }}"