Deploying This Site
This is a fairly basic example of what is possible with Ansible but it’s something I’ve been putting off for some time. I’m never going to accomplish large scale deployments if I can’t handle small ones first.
This reason for this post is because I was appalled at how I saw my users updating their sites and realized that my current process was no better. Nobody should have to deploy their site with the ‘drag & drop’ method. Here’s an example of this workflow.
- Download the current version of the site to a local directory.
- Make some changes to the site.
- Upload the new version of the site to the remote directory.
- Check to see if everything works as expected.
- Go back to step 2 if something is broken.
- Repeat every time you want to make a change
My Ideal Workflow⌗
Here’s the workflow I eventually want out of this series.
- Clone the current version of my site from my repository.
- Make some changes to the site.
- Test the changes in my development environment.
- When everything works then commit my changes.
- Push my changes to my repository.
And then behind the scenes a totally automated process takes over to actually build the site from my source and then place the result on my web server.
- My repository notifies my CI server that new changes have been pushed.
- My CI server pulls the latest changes and builds the site.
- If the site builds successfully then deploy the result to the production server.
What We’ll Accomplish⌗
I want to have something actually working so we’re going to condense some of the steps and make the deployment process more manual by forking the process at step 5.
- Run an Ansible playbook to notify my remote server to clone the current repository, build the site, and deploy it to production.
Setting Up⌗
Typically I have a deploy user on all of my servers for this purpose. I
designate /var/deploy
as the working directory for all of my deployment jobs so
in this case we’re going to use /var/deploy/estheruary.build
and
/var/deploy/estheruary.staging
for this process. We need to make sure both of
these directories are present.
vars:
build: /var/deploy/estheruary.build
staging: /var/deploy/estheruary.staging
tasks:
- name: create build and staging directories
file: >
dest={{ item }}
owner=deploy
group=deploy
mode=0750
state=directory
become: yes
with_items:
- "{{ build }}"
- "{{ staging }}"
Next we need to make sure that all the packages we need to run Jekyll are on the server. This isn’t the best solution because we shouldn’t have our development packages in the production environment. Later we will avoid this problem by using Docker on our CI server to create a consistent build environment.
We’re going to use Bundler to download all of our Ruby gems but we need all of
the following packages for bundle install
to complete successfully. I did not
anticipate that a static site generator needed so many native extensions.
tasks:
- name: install development packages
package: name={{ item }} state=installed
with_items:
- rubygem-bundler
- ruby-devel
- "@Development Tools"
- "@C Development Tools and Libraries"
- redhat-rpm-config
- zlib-devel
become: yes
Straight from the Source⌗
Now that we have a suitable build environment we need to acquire the source from our remote repository. Luckily Ansible has a Git module for precisely this purpose.
vars:
source: gogs@git.inspiredby.es:estheruary/estheruary.git
tasks:
- name: clone the build tree
git: >
repo={{ source }}
dest={{ build }}
accept_hostkey=yes
clone=yes
update=yes
register: estheruary_deploy_updated
The reason we’re registering the result of this module will become apparent soon. Technically speaking we could handle this with a handler (i.e. notify) but I don’t like to rely on handlers for control flow for the same reason you shouldn’t use exceptions for control flow.The reason we’re registering the result of this module will become apparent soon. Technically speaking we could handle this with a handler (i.e. notify) but I don’t like to rely on handlers for control flow for the same reason you shouldn’t use exceptions for control flow.
If You Build It⌗
Now that we have the source of our site into the build directory we’re ready to generate the site into our staging area; bundler and Jekyll make this process very easy.
tasks:
- block:
- name: install jekyll
shell: >
chdir={{ build }}
bundle install --frozen
- name: build the website
shell: >
chdir={{ build }}
bundle exec jekyll build --destination {{ staging }}
environment:
PATH: "{{ ansible_env.PATH }}:{{ ansible_env.HOME }}/bin"
The --frozen
option ensures that Gemfile.lock
isn’t automatically updated.
Note: This might be fixed with a configuration file later but when running via Ansible bundler doesn’t pick up bundler’s path addition so we have to add it manually.
Shipping it Out⌗
This one is trivial since we’re building the site on our production server. It’s just a simple matter of syncing the staging directory with the production location and making sure that the resulting files have the correct SELinux contexts.
vars:
target: /var/www/html
tasks:
- name: deploy the website
command: rsync -uax --delete {{ staging }}/ {{ target }}/
become: yes
- name: change ownership of the files
file: >
dest={{ target }}
owner=apache
group=apache
recurse=yes
state=directory
become: yes
- name: fix selinux permissions
command: restorecon -R {{ target }}
become: yes
when: estheruary_deploy_updated | changed
Actually Deploy a New Site⌗
Once you have your deployment host set up in your inventory the only thing remaining is to run the playbook.
ansible-playbook plays/estheruary-deploy.yml -K
Wrapping Up⌗
Success! If you’re reading this then our simple deployment pipeline has worked at least once. Stay tuned for the next installment where we separate out the build and deployment steps into their own environment.