Interacting with applications
When your application is prepared in containers you need to provide a starting
point or set of actions that the application is going to process whilst our
metric providers measure and read the metrics during this lifecycle.
In our terminology we call this a flow
.
In the simplest case this is just a start command that will kick of some workload
in one or more of the containers.
In a more complex form it is some kind of representation of a standard usage scenario
that the application will experience in production.
Here we will guide you through one simple example with
only one container that executes a terminal command.
Then we will show you one more complex example with an orchestration of
three containers, where we simulate a user that accesses a web application.
The result will be a usage_scenario.yml
file. (Specification →)
Command line app
We will make the most basic example our tool can handle:
- Base off of alpine base container.
- Add the package
stress-ng
fromapk
- Just run a 5 seconds stress run with default metrics providers
This is the resulting usage_scenario.yml
file:
name: Stress Container One Core 5 Seconds
author: Arne Tarara
architecture: linux
services:
simple-load-container:
image: alpine
setup-commands:
- apk add stress-ng
flow:
- name: Stress
container: simple-load-container
commands:
- type: console
command: stress-ng -c 1 -t 5
note: Starting Stress
The orchestration in the services
part uses the standard alpine image and
just installs an additional package into it: stress-ng
In the flow
part we tell the container in the commands
array that it shall
run a command of type console
and then provide the same string that we would type
in directly if we were running a bash shell (or similar).
This example is representative of machine learning applications where you typically
fire off one command (either train or infer) and then the applications runs one time
until it finishes.
Web app
In the sample for the web app we are reusing many parts of the compose.yml
file from:
Containerizing applications →
We then add a flow
part where we utilize a headless chrome browser to browse
some sample subsites of the web application.
It is important to note that the image names we use here in this example are
local docker images on our system, therefore the names can be different
on your system and application.
The names in this example follow the naming in the example from containerizing applications →
networks:
example-network:
services:
db-container:
image: demo_app_db
environment:
- MYSQL_ROOT_PASSWORD=somewordpress
- MYSQL_DATABASE=wordpress
- MYSQL_USER=wordpress
- MYSQL_PASSWORD=wordpress
networks:
- example-network
wordpress-container:
image: demo_app_wordpress
ports:
- 9875:9875
restart: always
environment:
- WORDPRESS_DB_HOST=db
- WORDPRESS_DB_USER=wordpress
- WORDPRESS_DB_PASSWORD=wordpress
- WORDPRESS_DB_NAME=wordpress
networks:
- example-network
puppeteer-container:
image: greencoding/puppeteer-chrome
setup-commands:
- cp /tmp/repo/puppeteer-flow.js /var/www/puppeteer-flow.js
networks:
- example-network
flow:
- name: Check Website
container: puppeteer-container
commands:
- type: console
command: node /var/www/puppeteer-flow.js
note: Starting Puppeteer Flow
read-notes-stdout: true
- type: console
command: sleep 30
note: Idling
- type: console
command: node /var/www/puppeteer-flow.js
note: Starting Puppeteer Flow again
read-notes-stdout: true
Let’s drill down on what is happening in this usage_scenario.yml
:
- First the network is set up
- Then all services are defined and on which network they can communicate
- This results in three services:
db-container
: Our mariadb database based off a local image (Server side)wordpress-container
: Our Wordpress based off a local image (Server side)puppeteer-container
: Our headless chrome browser based off our custom image on docker hub (client side)
- This results in three services:
- Then a flow is defined which triggers Node to run a sequential flow to be executed by the headless Chrome
- The flow runs one time, then a 30 second sleep occurs and then the flow is executed again
- The reason being that this simulates a user more typically. You browse, you read, your browse again … etc.
- The flow runs one time, then a 30 second sleep occurs and then the flow is executed again
In our example here we picture the puppeteer-flow.js
to be a file that you
already have present in you end-to-end tests and can directly reuse.
Communicating with containers inside the network
It is important that inside of this file you use the names of the containers as hostnames,
because otherwise they cannot be found through the network.
The hostname to find the Wordpress container in this example would be wordpress-container
.
In our example we also let it listen on port 9875 and it only accepts HTTP traffic.
A connect call would therefore be needed to be issued to: http://wordpress-container:9875
Here is an excerpt of an example puppeteer-flow.js
:
...
await page.goto("http://wordpress-container:9875", {
waitUntil: "networkidle2",
});
console.log(microtime.now()," Contact Page");
await page.click("a[href='http://wordpress-container:9875/?page_id=34']");