Going to production

Before we start, let's define some vocabulary that will be used in the next step.

Web App
Web App represents servers that will receive HTTP traffic from your users. They are able to run your application code.
Worker
Workers are servers dedicated to the execution of workflows and tasks. They have access to the same code as the Web App servers. Technically, they are not so different from Web App servers. They have the necessary software needed to run your application. The only difference is that they should not handle HTTP traffic from your users. It's more a conceptual difference, to be able to separate the load generated from HTTP traffic and the load generated by workflows and tasks, to make sure one will not have any performance impact on the other.
Agent
The Zenaton Agent means the software we distribute which is running on your servers (Web App and Workers). It's a binary, run as a daemon.

Dedicated workers setup

On both your Web App and your workers, you will need to:

  • have PHP installed;
  • have workflows and tasks' source code available;
  • have a Zenaton agent installed, configured and always running.

Network traffic

Traffic is always initiated by the Agent to Zenaton. You must open the following ports for the agent to work:

  • Outbound
    • 443/tcp: port used by the Agent to send requests to Zenaton API
    • 5672/tcp: port used by the Agent to send and receive instructions

Client Mode

Since we don't want any task execution to happen in your Web App, we will start the Agent there in "client mode". Client mode means that the Agent is able to dispatch workflows and tasks, send events, pause, kill and resume workflows, but will not handle any of the executions.

To start the Agent in client mode, just add the --client option during configuration:

zenaton listen --client --php
zenaton listen --client --node
zenaton listen --client --ruby
zenaton listen --client --python
zenaton listen --client --go

On your Workers, configure the Agent as usual:

zenaton listen --boot=boot.php
zenaton listen --boot=boot.js
zenaton listen --boot=boot.rb
zenaton listen --boot=boot.py
zenaton listen --boot=boot/boot.go

If you want to use your Web App as a Worker also, and thus avoid having more servers to manage, just configure your Agent in your Web App as if it was a Worker.


Environment

When deploying to production, make sure to use the production environment.

ZENATON_APP_ENV=production

Keep the Agent Running

To make sure your application is able to dispatch tasks and workflows at any time, you need to make sure the Agent is always running and listening using your application credentials.
To enforce this, you will need to use a software capable of monitoring the execution of a process and make sure it's always running. You can use systemd, supervisord, or any similar software to achieve this. Let's see how we can do that using systemd. You will need to run the following commands as user root.

First, create a systemd unit file:

touch /etc/systemd/system/zenaton-agent.service

Make sure the file permissions are correct:

chmod 664 /etc/systemd/system/zenaton-agent.service

Open the file we just created with your favorite text editor and add the following content:

[Unit]
Description=Zenaton Agent Daemon service
After=network-online.target

[Service]
Type=forking

# User and group that will be used to run your Zenaton Agent. User must have a Zenaton Agent installation
# in its home directory and have correct permissions to execute your sources.
User=...
Group=...

# Path to sources of your workflows & tasks.
WorkingDirectory=...

# Add your exact listen command
ExecStartPost=/usr/local/bin/zenaton listen --boot=...

ExecStart=/usr/local/bin/zenaton start
ExecStop=/usr/local/bin/zenaton stop
Restart=always

[Install]
WantedBy=multi-user.target

Make sure to replace ... with correct values.


Using Docker

Docker best practices recommend having only one running process in a container. This is what we are going to achieve. We will need to write a Dockerfile containing instructions to build a fully functional Zenaton Agent container.

In our container, we will need:

  • A base image being one of the supported Zenaton platforms.
  • The necessary binaries to be able to run our application code.
  • A zenaton agent installation.

We will install everything in our container, and start the zenaton agent when the container is started.

# Zenaton Agent Dockerfile

# Use composer as a base image: it will provides php and composer automatically for us and is based on ubuntu
# which is one of the supported platforms for Zenaton agent builds.
FROM composer/composer

# Install Zenaton
RUN curl https://install.zenaton.com | sh

# Copy files from our project into the container
COPY bin/ /app/bin
COPY src/ /app/src
COPY composer.* start_agent.sh /app/

# Change the working directory to the one we just created
WORKDIR /app

# Install project dependencies using composer and remove generated composer cache
RUN composer install --no-progress --no-suggest --prefer-dist --no-dev --optimize-autoloader \
&& rm -rf ~/.composer

# Set the PORT environment variable to be used by the agent
ENV PORT 4001

# Document the fact that the container will expose port 4001
EXPOSE 4001

# Set entrypoint to the agent start script
ENTRYPOINT ["./start_agent.sh"]

And the content of the start_agent.sh script, which will start the agent, run the listen command, and tail the zenaton.out file to make sure the container stays alive:

#!/bin/sh

set -e

zenaton start
zenaton listen --boot=src/bootstrap.php

tail -f zenaton.out

If you want an agent running in client mode, don't forget to add the --client option to the listen command in this script.

We can now build the docker image:

docker build --file Dockerfile-agent -t zenaton-agent-php .

The last thing you need to know is that by default, Zenaton libraries assume the agent is located on the same server as your web application. So it will try to make HTTP requests to http://localhost:4001/. When using a separate container for your agent, you won't have anything responding on this hostname and port. In your Web App containers, make sure you set the ZENATON_WORKER_URL environment variable to an ip/hostname and port of the agent container that must be reachable from your application container, e.g. http://zenaton-agent:4001.

In this example we did not generate a .env file inside the container. So when we run the container, we will need to supply the App ID, Api Token and App Env as environment variables for the agent to be able to authenticate itself.

docker run --init --rm -e ZENATON_APP_ID=<your app id> -e ZENATON_API_TOKEN=<your api token> -e ZENATON_APP_ENV=production zenaton-agent-php

That's it! Your agent container is now up and running.


Deploying to Heroku

To be able to use Zenaton in your Heroku hosted app, you will need to use our Heroku buildpack. The buildpack will install a Zenaton Agent inside your dynos, allowing your application to dispatch tasks and workflows, send events, etc. You will also be able to use your dynos as Zenaton workers.

Installation

To add the buildpack to your project, as well as the required environment variables, use the following commands:

cd <HEROKU_PROJECT_ROOT_FOLDER>

# If this is a new Heroku project
heroku create

# Add the appropriate language-specific buildpack
heroku buildpacks:add heroku/php

# Add Zenaton buildpack and set your Zenaton credentials
heroku buildpacks:add --index 1 zenaton/heroku-buildpack-zenaton
heroku config:set ZENATON_APP_ID=<ZENATON_APP_ID>
heroku config:set ZENATON_API_TOKEN=<ZENATON_API_TOKEN>
heroku config:set ZENATON_APP_ENV=production
# ZENATON_LISTEN_ARGS environment variable is used to give the parameters you want to use for the listen command of the Agent
heroku config:set ZENATON_LISTEN_ARGS="--boot=boot.php"

# Deploy to Heroku
git push heroku master
cd <HEROKU_PROJECT_ROOT_FOLDER>

# If this is a new Heroku project
heroku create

# Add the appropriate language-specific buildpack
heroku buildpacks:add heroku/node

# Add Zenaton buildpack and set your Zenaton credentials
heroku buildpacks:add --index 1 zenaton/heroku-buildpack-zenaton
heroku config:set ZENATON_APP_ID=<ZENATON_APP_ID>
heroku config:set ZENATON_API_TOKEN=<ZENATON_API_TOKEN>
heroku config:set ZENATON_APP_ENV=production
# ZENATON_LISTEN_ARGS environment variable is used to give the parameters you want to use for the listen command of the Agent
heroku config:set ZENATON_LISTEN_ARGS="--boot=boot.js"

# Deploy to Heroku
git push heroku master

Replace <ZENATON_APP_ID> and <ZENATON_API_TOKEN> with your Zenaton credentials.

When the deploy is finished, the Zenaton Agent will be automatically started when each dyno starts.
The agent is started in client mode in order to avoid adding load to your web dyno.

Worker dynos

If you want to use some dynos to perform the execution of workflows and tasks, you have to make some dynos using type zenatonworker. These dynos will start the Zenaton Agent in regular mode, executing workflows and tasks. In order to make these worker dynos, add them in your Procfile:

# Procfile
web: vendor/bin/heroku-php-apache2 web/
zenatonworker: tail -f /dev/null
# Procfile
web: node index.js
zenatonworker: tail -f /dev/null

Because the buildpack automatically starts the Zenaton Agent, you do not need to provide any specific command for the dyno, that's why we use tail -f /dev/null.