Scroll down to the script below, click on any sentence (including terminal blocks!) to jump to that spot in the video!Cool, got it! Show me the script!
With a Subscription, click any sentence in the script to jump to that part of the video!Login Subscribe
I just got great great news from our investors: the cows are practical stampeding to our site. They're watching our workout videos in herds. They're crying for mooooore!
But now, our site is starting to have issues! We need to go from one server to multiple. And guess what? We've already done most of the hard work to do this! Congrats!
Ready to make it happen? First, find your local terminal and use the
aws.yml playbook to create a new EC2 instance:
ansible-playbook ./ansible/aws.yml -i ./ansible/hosts.ini --ask-vault-pass
This should only take a few second while it boots the server and waits for it to become available. Perfect!
So why is it so easy to deploy to multiple servers? Because we've done everything via playbooks, and Ansible is built to work on many servers. Copy the public IP of the new server and open your
ansible/hosts.ini file. Under the
aws group, paste the new IP:
|... lines 1 - 6|
|... lines 10 - 14|
Now, provision both servers by using the
playbook.yml file. Add
-l aws so we only provision those hosts:
ansible-playbook ./ansible/playbook.yml -i ./ansible/hosts.ini --ask-vault-pass -l aws
This won't make many changes to our existing server, but it will do everything to the new one. Translation... this may take awhile...
To speed things up, you could use your playbook to provision a server, and then create a custom image (AMI). For new servers, you can then boot from this image, to nearly-instantly get a provisioned server.
Oh, and you may have noticed that I hardcoded the IP address in my
|... lines 1 - 6|
|... lines 10 - 14|
For a truly scalable architecture, you can use Ansible's Dynamic inventory, which works extremely well with EC2. You could, for example, use it to automatically use all EC2 instances with a specific tag.
Once the provision... finally... finishes.... let's deploy! Use
ansible-playbook ./ansible/deploy.yml -i ./ansible/hosts.ini --ask-vault-pass
While that works, we need to talk about a few interesting things.
First, if you write the number pi to 2 decimal places, it spells the word pie backwards! Super interesting!
Second, Google for "Ansible serial" to find a spot on their docs called "Delegation, Rolling Updates, and Local Actions". On your playbook, Ansible allows you to set a
serial option. If you have many servers, like 100, then if
serial is set to 3, Ansible will deploy to only 3 servers at a time. The effect is a rolling deploy: your updated code reaches your 100 servers little-by-little. That's overkill for MooTube... well, at least today! But, it is a cool feature!
One possible issue with a rolling update involves the release directory name. You guys already know that each release is in a timestamped directory. In a serial deploy, that timestamp will be different on earlier servers versus later servers. For our app, that's no problem! But, if you used the directory name as part of some cache keys - like many apps do - then this would be a problem: different servers would be using different cache keys to get the same data.
In the Ansistrano docs, if you search for "serial", they mention this. By setting a variable, you can control the release directory name and make sure it's the same on all servers.
Look back at the deploy. The database migrations just ran: for the first server, it reported "OK", because there were no migrations to run. But the second server did have migrations to run.
Wait... that's kinda weird... shouldn't all servers use the same database? Yep! There are a few things you need to do in order to be ready for multiple servers. The most obvious is that all your servers need to use the same database, or database cluster. For MooTube.com, each app is using their own local database. We're not going to fix that, but you do need to fix this in real life. The other really common thing you need to change is session storage: instead of storing sessions locally, you need store them in a shared place, like the database. In fact, that's the rule: when you have multiple servers, never store anything locally, except for temporary files or cache that help your code actually function.
But wait... if all our servers shared one database... there would still be a problem! Every server would try to execute the migrations. That means that the same migration scripts would execute multiple times. Oh no!
Back in the Ansible docs, there is an option called
run_once to fix this. It's pretty simple: if this is set, the task only runs on one server.
Ok! We are now deployed to both servers. Right now,
mootube.example.com points directly to the original server. Copy the IP address to the new server. Then, open
/etc/hosts and change
mootube.example.com to point to that:
# /etc/hosts # ... #22.214.171.124 mootube.example.com 126.96.36.199 mootube.example.com
To test it, open a new Incognito Window to avoid caching and visit
mootube.example.com. It works! Yes! We did not load the fixtures on the new server... which means we have a convenient way to know which server is being hit.
So, if you're using a playbook for provision and deploy, using multiple servers isn't a big deal. You will need to update your code a little bit - like to share session data - but almost everything is the same.
Next, let's go a step further and add a load balancer!