6 must-do before hosting your Laravel web platform on AWS
As a consultant, my clients often ask me whether it is worth porting their web application to Amazon Web Services. They are expecting better performance and reliability because AWS sounds more professional, but they’ve also heard stories about horrifying AWS bills, even though they’re not sure why.
The short answer is, by architecting your web platform the right way, you will be able to not only deploy on a cheaper hosting option, but also ensure a smooth migration to AWS down the road, zero refactoring guaranteed.
The long answer is why I’m writing this post! In the rest of the post, I will offer an extensive checklist that will guarantee your web platform to be portable and allow you to make the most of AWS.
1. Configuration as environment variables
The first thing I would check during an audit is whether the app configuration is retrieved from the environment variables. The environment is wherever your app could be running. It could be your developer’s local machines, your testing servers, your staging servers, or your production servers.
An application configuration is anything that might change between environments.
- Resource handles to backing services (URIs to database, cache engine, search engine etc)
- Any credentials to external services (passwords, SSH keys, SSL certificates)
Retrieve your configuration from predefined files…
… and the config files should be initialized from environment variables.
In PHP Laravel, this can be achieved by creating files in the config folder, and initializing the config files with environment variables, using the helper env().
And initialize your config files from the environment
The environment-specific configuration should not be in the source code, but rather stored in the environment.
On your test, staging, and production servers, the environment is set by your deployment scripts.
On the developer’s local machines, and for the Laravel framework, it is stored as an .env file at the root of the project, that should never be committed to the source code repository (add it to your .gitignore file).
Configuration for your test pipelines can be stored in the .env.testing file.
Any code that tests the environment is a red flag (ifs production then… / if staging then). When local and production and staging are using different drivers (for example dev machines are storing files locally, whereas production server use a file storage service), an abstraction layer should be used. With that said, it should not be mixed in the code with your application business logic.
Laravel offers services facades for Storage, Cache, Queue, etc., their role is to make abstraction of how file storage, cache, background jobs queuing, etc. are achieved so the logic is independent to the environment, and so your code should be.
Doing so, the same code will use a local Redis server to store the web sessions and a managed service ElastiCache on AWS.
There will be no last minute switch when deploying your code to production, and no last minute mistake.
2. Stateless code
A stateless app is an application program that does not record data generated in one session — such as information about user settings and events that occurred — for use in the next session with that user.
Of course your application won’t be stateless! If you build a web platform, you will usually want your users to generate as much data as possible on your service.
But its processes should be stateless, in the sense that redeploying, crashing, or load balancing a user to different servers should not affect the user.
This is achieved by centralizing storage only in back-end services, not on the application servers. By storing your sessions in a managed Redis service (like AWS ElastiCache), keeping your user files on a file storage service (like AWS S3), and running your database on a managed AWS RDS instance, you ensure that redeploying on one application server will not interrupt your users sessions.
It also means that you can load balance your traffic on multiple application servers and your users will always be able to find their files. They will find them even after a deployment failed and they lose a server.
Smell-test: code using local files, local memory
A PHP application that stores files on the local server it’s running on is another red flag. If you were to lose that server on the next redeployment, you’d lose user data. Such application will not be ready for scaling and will need refactoring too.
For user sessions and API OAuth tokens, you can even handle user session data without a centralized key-value store or using the database. By using JSON Web Tokens instead of random OAuth tokens, an application will rely on the client (browser) to store and send all this information back. The data is encrypted server-side to avoid tampering, and could be stored in a cookie. In that case, the servers would store nothing (or just the black-list for revocation), and the application processes would not have to share anything or call a back-end service.
3. Allow concurrency
Now that your app is stateless, an immediate benefit is that you could run multiple versions of it, on different servers. This is known as horizontal scaling, as oppose to vertical scaling, which would mean keeping and running your application on one single server, and to upgrade it to a more powerful one when needed.
Not all software can be horizontally scaled (for example, it’d be a complex problem for databases or search engines), but, just by following the 6 points in this guide, you can immediately horizontally scale PHP apps.
Once your application is stateless, the next step is to separate the front-end code to the background tasks. Web processes are processes that respond immediately to your users’ browsers requests.
You might also have background jobs (like sending reports, generating invoices, crunching data, send email/push notifications). They should be executed outside of your web processes, even when they are written in PHP as well.
These jobs are to be executed by worker processes so to not block web requests.
In the Laravel framework, use the Queue facade and a message queue server like Redis to dispatch these blocking tasks to a separate pool of servers. When you migrate to AWS, you can use the managed message queue service SQS.
Warning: if you’re using AWS SQS to schedule critical tasks, you need to be aware of the duplicate message edge case as described by Buffer.com in this post.
4. Logs as streams, not files
Just like user generated files should not be stored locally on your app server, logs should not be stored locally either. They should be centralized and streamed to a back-end service, where you’ll find all the logs from all your application servers in one place (even from the servers you lost or took down).
Laravel’s Log facade only stores logs in files by default, so you would need a custom configuration to stream them to PHP’s standard error output instead. If you’re running your PHP application inside Docker, you can then make use of the AWS CloudWatch driver and stream your logs to one centralized place.
Any log written into a local file on a production server.
5. Horizontal scaling, without refactoring
If you’ve followed all the points above, congratulations! Your web application is ready for scaling, no refactoring required.
By separating the configuration from the code, the application becomes portable from a cheaper hosting provider to AWS.
Dispatching the blocking tasks to background workers processes allows us to offload the front-end and scale it independently to the workers processes.
By running our application as stateless processes, we can load-balance our users traffic to different servers, and store the data in backend-services.
These are the pre-requisites to horizontal scaling. It is cost-efficient because you can scale with smaller less expensive servers.
The next step is to host your back-end services in a cost-efficient way too.
6. Leverage AWS Managed Services
So far, we’ve only talked about application servers (where your HTTP servers respond to web requests by executing your PHP code), so what about the back-end services, the database, the search engine, and so on?
That’s where you can make the most of AWS. All these back-end services exist as managed services on AWS, i.e. a pay-as-you-go billing with no server for you to maintain.
You want to use these services as much as possible for the database (AWS RDS, DocumentDB), file storage (S3), session storage (ElastiCache), logs aggregation (CloudWatch), cache engine, search engine (AWS Managed ElasticSearch), and load-balancing.
So what’s with the cost-efficiency?
Managed services cost much less in practice than running your own servers. They are more secure and can be scaled in a few clicks. Storing your user files on AWS S3 costs about $0.02 per GB per month after 15GB transfer per month, and is free before that.
Hosting your search engine on AWS ElasticSearch managed service is free for the smaller instance.
There are many examples I can come up with, but I think you get the point.
More importantly, managed services take much less time to setup and require virtually zero time for maintenance, which can definitely get you a better night of sleep.
I hope this post was helpful to you! If you have any questions of feedback, feel free to leave a comment below!
This post was originally published by the author here. This version has been edited for clarity and may appear different from the original post.