Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Deploying to Multiple Servers

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $9.00

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!

Ahem.

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!

Provision (Literally) all the Servers!

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
[aws]
54.80.32.36
54.81.150.15
... 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...

Tip

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.

Dynamic Inventory

Oh, and you may have noticed that I hardcoded the IP address in my hosts.ini file:

... lines 1 - 6
[aws]
54.80.32.36
54.81.150.15
... 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 deploy.yml:

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.

Preparing for Multiple 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.

Migrations & run_once

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
# ...
#54.80.32.36 mootube.example.com
54.81.150.15 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!

Leave a comment!

8
Login or Register to join the conversation

Hi SymfonyCast Team:

Videos on chapter 20 and 21, cann't be played, HD or SD
404 Not Found
https://player.vimeo.com/ex...
https://player.vimeo.com/ex...
Regards

Reply

Hey Omar,

We're sorry for this! I just fixed this, now the videos are playable for me. Thank you for reporting this! Please, let us know if you still can't play them.

Cheers!

Reply

Thanks!

Sure no problem, I check the videos and now i can play them 👍...

And before the fix having the option to download videos is great to sort this kind of things.

Reply

Hey Omar,

Ah, yeah, download is always a good fallback option in case of any issues to play the video on the website, good catch! :)

And thanks for confirming it works for you now!

Cheers!

Reply
Cesar Avatar

Hello guys,
Is there a way to deploy a symfony app using containers? I normally deploy them using a normal EC2 server but now I have this requirement from one member of our team. I don't know much about this type of architecture but I think I would need to split my app in microservices.
I hope you can give me some tip.
Cesar

Reply

Hey Cesar!

Sorry for the slow reply! Unfortunately, I can't offer much specific guidance about Docker & containers (we use Docker a bit locally, but not on production). But I can offer you two things:

1) API Platform ships with full Docker support and they have information about deployment with Docker. I'm not saying you should use API Platform (you can, but that's not the point) - just that they have a nice guide that might be relevant to you - https://api-platform.com/do...

2) Here at SymfonyCasts, we have 3 micro-services that support our main application. We deploy all of this via SymfonyCloud, which we love. It uses containers behind the scenes, but most/all of the complexity is abstracted away from us :).

Good luck!

Reply
Cesar Avatar

Thanks for answering Ryan.
Are you planning to do a screencast of Symfony Cloud?

Reply

Yes! It's on our list, but we don't have a timeline yet (which means it's not in the top few tutorials on our list). However, I'd be happy to answer questions about it if you have them :).

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

While the fundamentals of Ansistrano haven't changed, this tutorial is built using Symfony 3, which has significant differences versus Symfony 4 and later.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/orm": "^2.5", // v2.7.2
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "sensio/distribution-bundle": "^5.0.19", // v5.0.20
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "symfony/monolog-bundle": "^3.1.0", // v3.1.0
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "symfony/swiftmailer-bundle": "^2.3.10", // v2.6.3
        "symfony/symfony": "3.3.*", // v3.3.5
        "twig/twig": "^1.0||^2.0", // v1.34.4
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "predis/predis": "^1.1", // v1.1.1
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0", // v3.3.5
        "doctrine/data-fixtures": "^1.1", // 1.3.3
        "hautelook/alice-bundle": "^1.3" // v1.4.1
    }
}

What Ansible libraries does this tutorial use?

# ansible/requirements.yml
-
    src: DavidWittman.redis
    version: 1.2.4
-
    src: ansistrano.deploy
    version: 2.7.0
-
    src: ansistrano.rollback
    version: 2.0.1
userVoice