Setting up Vagrant
Did your colleague ever tell you I am unable to run the app on my computer because … or It works on my machine? From now on, this is never going to be a problem again.
What is Vagrant?
Vagrant is a tool for building and managing virtual machine environments. Vagrant is not a virtualization tool. Therefore, you have to get some.
Vagrant comes with out-of-the-box support for Virtualbox which is the most popular, free, and cross-platform. However, it is not the only option. Vagrant also works with VMware, Hyper-V, and Docker.
Advantages
- Reproducible and portable environment
- Automation
- Same environment as production server
- All configuration files are plain text files (VCS love it!)
- Developer works in his favourite OS, the app runs in a virtual environment
- Fast start for a new team member
Installation
It is not recommended to install Vagrant using package managers. Use direct download instead.
Install Virtualbox (or else).
Install Vagrant using a binary package. Debian, Windows, Centos, and Mac OS X systems are supported.
Getting boxes
A box is an image of a virtual machine. You can also create your own box if you desire to do so. However, for most cases, it is a huge speedup for you and your team to utilise already made boxes.
Adding a box
Naming convention of boxes is following: <username>/<box-name>
Let’s say we have chosen the ubuntu/xenial64
box. We could use the box right a way but it is useful to add the box to Vagrant so that multiple Vagrant environments can reuse it (It will take a while to download the box).
$ vagrant box add ubuntu/xenial64
Now we can verify that the box was added.
$ vagrant box list
Initialise a project with Vagrant
First, move to the root of your project. Then execute following:
$ vagrant init ubuntu/xenial64
This will create a Vagrantfile
with content:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
end
Surprising but that’s all.
Running a VM and accessing it
Now we are ready to boot the Vagrant environment.
$ vagrant up
There is no UI to access the VM. Therefore, we use the good old SSH.
$ vagrant ssh
How do I transfer files to VM?
Everything in your project folder is automatically synchronised with VM by Vagrant. It appears in /vagrant/
folder.
Let’s try it.
1) Create a new file in your project folder (not in VM)
$ echo "Hello world!" > hello.txt
2) Then go to VM.
$ vagrant ssh
3) List files in /vagrant/
directory.
ubuntu@ubuntu-xenial:~$ ls -la /vagrant/
total 68
drwxrwxr-x 1 ubuntu ubuntu 4096 Jul 26 18:26 .
drwxr-xr-x 24 root root 4096 Jul 26 18:08 ..
drwxrwxr-x 1 ubuntu ubuntu 4096 Jul 26 18:06 .vagrant
-rw-rw-r-- 1 ubuntu ubuntu 3022 Jul 26 17:58 Vagrantfile
-rw-rw-r-- 1 ubuntu ubuntu 13 Jul 26 18:26 hello.txt
-rw------- 1 ubuntu ubuntu 46138 Jul 26 18:08 ubuntu-xenial-16.04-cloudimg-console.log
Update your box
The following command will download the new version for your box.
$ vagrant box update
Note that updating the box will not update an already-running Vagrant machine. To reflect the changes in the box, you will have to destroy and bring back up the Vagrant machine.
If you want just to check for updates:
$ vagrant box outdated
Since updating installs new boxes (and leaves the old ones) it is a good practice to remove old boxes from time to time.
$ vagrant box prune
Finishing the work
We can either shut the VM down, suspend or destroy.
Shut down
$ vagrant halt
Halting the virtual machine shuts down the guest operating system and powers down the guest machine. You can use vagrant up
when you are ready to boot it again.
Suspend
$ vagrant suspend
Suspending saves the current running state of the machine and stops it. When you are ready to begin working again, just run vagrant up
, and it will be resumed from where you left off.
Destroy
$ vagrant destroy
Destroying the virtual machine removes all traces of the guest machine from your system. It’ll stop the guest machine, power it down, and remove all of the guest hard disks.
CLI overview
vagrant box add <username>/<box-name>
– Add a boxvagrant box list
– List all the boxes that are installedvagrant box update
– Update the box for the current environment if there are updates availablevagrant box outdated
– Check if the box you are using in your current environment is outdated.vagrant box prune
– Remove old versions of installed boxesvagrant up
– Create and configure guest machine using yourVagrantfile
vagrant halt
– Shut down the running machinevagrant suspend
– Suspend the guest machinevagrant destroy
– Stop the running machine and destroy all resources that were created during the machine creation process.
Conclusion
Setting up a development environment with the virtual machine is a piece of cake with Vagrant. Next time I am going to show you how to set up a LAMP environment.
Sources
A cron basics
What is a cron? and How can we use it in web development? are the most common questions people ask. I am going to answer these questions and discuss issues you might encounter.
Glossary
- A cron is a programme which executes programmes according to schedule. It is a time-based job scheduler.
- A cron task is a programme being executed by cron.
- A crontab is a file which contains the schedule of cron tasks to be run and at specified times.
Setting up a cron
Crontab commands
crontab -e
– Edit crontab file, or create one if it doesn’t already exist.crontab -l
– Crontab list of cron jobs, display crontab file contents.crontab -r
– Remove your crontab file.
Crontab syntax
* * * * * command to be executed
- - - - -
| | | | |
| | | | +----- day of week (0 - 6) (Sunday=0)
| | | +------- month (1 - 12)
| | +--------- day of month (1 - 31)
| +----------- hour (0 - 23)
+------------- min (0 - 59)
Even though the syntax is simple it is much better to use Online Crontab Expression Editor, especially for beginners.
Write a script
It can be basically anything executable on a given system (Bash, Python, PHP, Ruby, C , etc.). It should behave like a normal programme – return zero on success and return non-zero value otherwise.
Use case
Let’s say we are building a simple mailing service [1]. A task is to send emails to lots of people (~1000) at once eg. newsletter, special offer.
An obvious solution - loop
An obvious solution is to loop through all email addresses and send all emails on the push of the button.
for ($emails in $recipient) {
$this->sendMail($sender, $recipient, $subject, $content);
}
However, this solution has a flaw – too many emails are being sent during a short period of time. Mail servers do not like that, especially, if you are using the basic ones build in the programming language. It also takes a very long time.
Cron solution
We can create a simple queue of emails using a database:
CREATE TABLE `email` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sender` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`recipient` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`subject` longtext COLLATE utf8_unicode_ci NOT NULL,
`content` longtext COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Such queue contains mails which are to be sent. Once they are sent, we delete them. Sending script might look like this using Nette and Symfony Console:
<?php
namespace EmailModule\Console;
use Doctrine\ORM\ORMInvalidArgumentException;
use EmailModule\Entity\Email;
use EmailModule\Facade\EmailFacade;
use Nette\InvalidStateException;
use Nette\Mail\Message;
use Nette\Mail\SendmailMailer;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class EmailCommand sends at most 50 oldest emails from email queue.
*
* @package EmailModule\Console
*/
class EmailCommand extends Command
{
/**
* Command configuration.
*/
protected function configure()
{
$this->setName('app:email')
->setDescription('Sends enqueued emails');
}
/**
* Command execution routine.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int return status
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var EmailFacade $emailFacade */
$emailFacade = $this->getHelper('container')->getByType('EmailModule\Facade\EmailFacade');
$emails = $emailFacade->getEmailsToSend();
$sentCount = 0;
$responseStatus = 0;
// Try to send all emails
foreach ($emails as $email) {
/** @var Email $email */
$mail = new Message();
$mail->setFrom($email->getSender());
$mail->addTo($email->getRecipient());
$mail->setSubject($email->getSubject());
$mail->setHtmlBody($email->getContent());
$mailer = new SendmailMailer;
try {
$mailer->send($mail);
$sentCount++;
$emailFacade->dequeueEmail($email);
} catch (InvalidStateException $e) {
$output->writeLn('<error>' . $e->getMessage() . '</error>');
$responseStatus = 1;
} catch (ORMInvalidArgumentException $e) {
$output->writeLn('<error>' . $e->getMessage() . '</error>');
$responseStatus = 1;
}
}
$output->writeLn("");
$output->writeLn($sentCount . "/" . count($emails). ' emails sent.');
return $responseStatus;
}
}
It loops through emails and tries to send them. Now we add a cron task. We want to execute the task with some reasonable period. For example, every 15 minutes send up to 50 emails, that means at most 200 emails per hour (Feel free to modify these numbers to suit your use case). Crontab entry looks like this:
*/15 * * * * /home/www/mail_service/app/console app:email
Pitfalls
- Cron task might fail.
- Crontab entry might not be set (programmer forgot).
- Crontab entry might be set incorrectly (typing error)
- Crontab must end with empty line
Conclusion
As you can see, setting up a cron task is simple. First, you write a programme in your favourite language, then you figure out when it should be executed and then you set the cron task up.
Sources
Footnotes
-
Actually, it is not a very good idea to create a mail sending service like this. To know why read 5 subtle ways you’re using MySQL as a queue, and why it’ll bite you. It is much better to use message queue like RabbitMQ; ↩