Skip to content

Instantly share code, notes, and snippets.

@chaddupuis
Last active December 21, 2022 19:15
Show Gist options
  • Save chaddupuis/dc6d530e7672403943b0db47c24a3245 to your computer and use it in GitHub Desktop.
Save chaddupuis/dc6d530e7672403943b0db47c24a3245 to your computer and use it in GitHub Desktop.
Ansible to push code updates for Django running with unicorn/nginx behind a cloudflare load balancer

An ansible run that will push out repo updates to a pool of nginx/gunicorn servers.
Handles virtualenv and setting the correct Django environment (ie production, staging, etc.)

  • uses ansible vault for the variables files. Does some other firewalld changes to lock down the web servers.

In this case they are behind a cloudflare loadbalancer so the CF server certificate is written to them.
This is a "second stage" playbook, the first being more generic server setup (ssh credentials, basic packages, etc.)

Would be initiated along the lines of:

ansible-playbook -i your inventory.yaml -l whatservers push-django-updates.yaml --ask-become-pass --ask-vault-pass (to open variables and any other encrypted files)

- hosts: webservers
become: yes
vars:
my_ip_range: x.x.x.x/24
my_db: x.x.x.x
#following from cloudflare.com/ips
cf_1: 173.245.48.0/20
cf_2: 103.21.244.0/22
cf_3: 103.22.200.0/22
cf_4: 103.31.4.0/22
cf_5: 141.101.64.0/18
cf_6: 108.162.192.0/18
cf_7: 190.93.240.0/20
cf_8: 188.114.96.0/20
cf_9: 197.234.240.0/22
cf_10: 198.41.128.0/17
cf_11: 162.158.0.0/15
cf_12: 104.16.0.0/13
cf_13: 104.24.0.0/14
cf_14: 172.64.0.0/13
cf_15: 131.0.72.0/22
vars_files:
- vars-workers.yaml
pre_tasks:
- name: "Install packages - nginx gunicorn python3 tools"
apt:
pkg: ['python3-dev', 'python3-setuptools', 'python3-venv', 'nginx', 'gunicorn3', 'curl']
state: present
- name: Check if a reboot is needed for Debian-based systems
stat:
path: /var/run/reboot-required
register: reboot_required
tasks:
- name: Create directories with permissions for venv
file:
path: /site/dj
state: directory
owner: www-data
group: www-data
mode: 0775
recurse: yes
# copy git files
# obtain deploy key from
# https://gitlab.com/*\\\you///*/*\\\project///*/-/settings/repository
- name: Clone the main branch
git:
repo: https://{{gl_read_un}}:{{gl_read_key}}@gitlab.com/you/your-repo.git
dest: /site/dj/
version: main
force: yes
- name: Create virtualenv
shell: "/usr/bin/python3 -m venv /site/dj"
become: yes
become_user: www-data
- name: Install wheel, psycopg2-binary, gunicorn pip into virtualenv
pip:
name:
- wheel
- psycopg2-binary
- gunicorn
virtualenv: /site/dj/
- name: Install requirements.txt
pip:
state: latest
virtualenv: /site/dj/
requirements: "/site/dj/requirements.txt"
virtualenv_python: python3
# handle environmental variables
- name: Install env files
copy:
src: "current.env"
dest: /site/dj/.env
owner: root
group: root
mode: 0644
- name: Switch environment to "s" or "p"
lineinfile:
path: /site/dj/.env
regexp: '^ENVIRONMENT='
line: ENVIRONMENT="p"
# switch to staging/prod/etc.
#before this should be nginx gnuicorn
- name: Copy gunicorn service systemd
copy:
src: "gunicorn.service"
dest: /etc/systemd/system
owner: root
group: root
- name: Copy gunicorn socket systemd
copy:
src: "gunicorn.socket"
dest: /etc/systemd/system
owner: root
group: root
# - name: Start gunicorn service
# systemd:
# name: gunicorn.service
# state: started
# enabled: yes
- name: Start gunicorn socket
systemd:
name: gunicorn.socket
state: started
enabled: yes
- name: Make Cert Directory
# /etc/ssl/cf-origin-cert.pem;
# /etc/ssl/cf-origin-key.pem;
file:
path: "/etc/ssl"
state: directory
mode: '0500'
- name: Copy CF Origin Cert
copy:
src: "cf-origin-cert.pem"
dest: /etc/ssl/cf-origin-cert.pem
owner: root
group: root
mode: '0400'
- name: Copy CF Origin Key
copy:
src: "cf-origin-key.pem"
dest: /etc/ssl/cf-origin-key.pem
owner: root
group: root
mode: '0400'
- name: Remove default nginx sites-enabled
file:
path: "/etc/nginx/sites-enabled/default"
state: absent
- name: Copy our nginx django sites available
copy:
src: "your-production.nginx.conf"
dest: /etc/nginx/sites-available/yoursite
owner: root
group: root
- name: Enable nginx virtual host
file:
src: /etc/nginx/sites-available/yoursite
dest: /etc/nginx/sites-enabled/yoursite
state: link
- name: Restart Nginx
service: name=nginx state=restarted enabled=yes
# should be near the end
# changes all permissions in the sites area to www-data
- name: Fix permissions on site/dj files
file: dest=/sites/dj/yourfiles owner=www-data group=www-data recurse=yes
# need the following for the pip libraries to be writeable
- name: Fix permissions on site/dj/lib files
file: dest=/site/dj/lib owner=www-data group=www-data mode=0777 recurse=yes
# Now lock it down
- name: Add sources in internal zone CF, and personal access
shell: |
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{my_ip_range}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{my_db}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_1}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_2}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_3}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_4}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_5}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_6}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_7}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_8}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_9}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_10}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_11}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_12}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_13}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_14}}"
/bin/firewall-cmd --permanent --zone="internal" --add-source="{{cf_15}}"
/bin/firewall-cmd --reload
# when: zone_status.stdout == 'not-configured'
- name: Open http service on internal zone
firewalld:
service: http
zone: "internal"
state: enabled
permanent: yes
immediate: yes
notify:
- restart firewalld
- name: Open https service on internal zone
firewalld:
service: https
zone: "internal"
state: enabled
permanent: yes
immediate: yes
notify:
- restart firewalld
- name: Open http/80 port on internal zone
firewalld:
port: "80/tcp"
zone: "internal"
state: enabled
permanent: yes
immediate: yes
notify:
- restart firewalld
- name: Open http/443 port on internal zone
firewalld:
port: "443/tcp"
zone: "internal"
state: enabled
permanent: yes
immediate: yes
notify:
- restart firewalld
- name: Add ssh to internal zone
firewalld:
service: ssh
zone: "internal"
state: enabled
permanent: yes
immediate: yes
notify:
- restart firewalld
- name: Remove ssh from public zone
firewalld:
service: ssh
zone: "public"
state: disabled
permanent: yes
immediate: yes
notify:
- restart firewalld
# restart services - to get latest code push...
- name: Restart gunicorn.socket
systemd:
name: gunicorn.socket
state: restarted
daemon_reload: yes
- name: Restart gunicorn.service
systemd:
name: gunicorn.service
state: restarted
daemon_reload: yes
- name: Reboot the server if needed.
reboot:
msg: "Reboot initiated by ansible - reboot required file"
connect_timeout: 5
reboot_timeout: 600
pre_reboot_delay: 0
post_reboot_delay: 30
test_command: whoami
when: reboot_required.stat.exists
- name: Setup Cron Job for Django Management Command
cron:
name: "run my mgt command"
user: "root"
weekday: "*"
minute: "0"
hour: "8,10,12,14,16,18,20"
job: "cd /site/dj/yoursite && /site/dj/bin/python /site/dj/yoursite/manage.py YourCommand"
state: present
run_once: true
delegate_to: "{{groups['yourwebservers'][0] }}"
# groups [0] above chooses only the first server to run cron jobs otherwise it would be all
- name: Remove old packages from the cache.
apt:
autoclean: yes
- name: Remove dependencies that are no longer needed.
apt:
autoremove: yes
purge: yes
handlers:
- name: restart firewalld
service:
name: firewalld
state: restarted
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment