Project YAML Reference

  • All YAML field names should be lowercase (See: https://github.com/go-yaml/yaml/issues/156)
  • Default file name is maelstrom.yml
  • YAML file will be bound to the yamlProject struct here (use this for canonical reference)

Using maelctl

Use maelctl project put to apply changes in maelstrom.yml to the target maelstromd process

# running maelctl with no args outputs help
$ maelctl

# basic usage - assumes file is maelstrom.yml
$ maelctl project put

# use --file to specify a different yml file
$ maelctl project put --file=my-project.yml

# dry run mode - outputs diff of changes but doesn't make any changes
$ maelctl project put --diffonly

Basic Structure


---
# Project name
name: myproject
# If set, name=value environment variables will be loaded from this file and set in the
# environment of runnning containers
# NOTE: this does NOT load variables for purposes of ${FOO} variable interpolation
#       in this YAML file. Interpolation is done based on env vars in scope in the shell
#       that invokes "maelctl project put".  If you wish to scope in env vars from a file
#       for purposes of interpolation, use the "--env=filename" switch. For example:
#       "maelctl project put --env=interpolate-vars.env"
envfile: /path/to/file
# Environment variables that will be set in all component containers
environment:
  - ENV_VAR1=value1
  - ENV_VAR2=value2
components:
  # component name:
  component_name_1:
    # docker image to use
    image: foo/bar:mytag
    # command to run
    command: ["python", "-u", "/app/detector.py"]
    # env vars for this component. if any overlap with project, component values win
    environment:
      - MORE_ENV=here
      - ENV_VAR2=this_overrides_project_setting
    # event sources for this component
    eventsources:
      component1_http:
        # only specify one block (http, cron, or sqs)
        http:
          hostname: component1.example.com

Component

Scaling and resource options


---
name: myproject
components:
  component_name_1:
    # Soft limit - MiB of RAM to reserve per container (docker run: --memory-reservation)
    # Optional. Default = 128
    reservememory: 2048
    # Hard limit - MiB of RAM. (docker run: --memory)
    # Optional. If not set, no memory limit is set on the container.
    limitmemory: 4096
    # CPU share to give each container (docker run: --cpu-shares)
    # Optional. If not set, then this flag is not specified on the container.
    # Docker default is 1024, so a value of 2048 would be 2x weight
    cpushares: 512
    # Maximum concurrent requests to send to a single container
    # Optional. Default = 1
    maxconcurrency: 5
    # If true, maxconcurrency will be used influence autoscaling, but will not
    # be used to queue requests over the limit. Requests to this component will
    # be load balanced, but will passed through with no limits.
    # Optional. Default = false
    softconcurrencylimit: true
    # Minimum instances of this component to run at any time
    # Optional. Default = 0
    mininstances: 1
    # Maximum instances of this component to run at any time across all nodes
    # Optional. Default is unlimited.
    maxinstances: 30
    # Maximum instances of this component to run on a single node
    # Optional. Default is unlimited.
    maxinstancespernode: 3
    # Maximum seconds a request may take to complete, including wait time if
    # max concurrency is reached. Optional. Default = 300
    maxdurationseconds: 60
    # Seconds a container must be idle before it is eligible to scale to
    # zero running instances. Optional. Default = 300
    idletimeoutseconds: 120
    # If the % of maxConcurrency of all instances of this component is lower than this
    # value, instances of the component will be stopped (respecting mininstances if > 0).
    # Value should be 0..1 (e.g. .5 = 50%). Default = 0.25
	scaledownconcurrencypct: 0.25
    # If the % of maxConcurrency of all instances of this component exceeds this value,
    # more instances of the component will be started (respecting maxinstances if > 0).
    # Value should be 0..1 (e.g. .5 = 50%). Default = 0.75
	scaleupconcurrencypct: 0.75

Restart options

Use the restartorder and startparallelism features to control how rolling deploys are executed.


---
name: myproject
components:
  component_name_1:
    # restartorder
    #   Informs how updates to a new version should be performed.
    #   Valid values: startstop, stopstart
    #
    # If "startstop" a new container is started and health checked, then the old container
    # is stopped.
    #
    # If "stopstart" the old container is stopped, then the new container is started.
    #
    # "startstop" will result in faster upgrades to new versions and in single instance
    # cases will avoid request pauses during restarts, but it will lead to temporary
    # increased memory usage since the two containers (old and new) will briefly run
    # simultaneously.
    #
    # Default = stopstart
    restartorder: startstop
    #
    # startparallelism
    #   Valid values: parallel, series, seriesfirst   (Default = parallel)
    #
    #   parallel:
    #   Start (or restart) components fully parallel (no coordination) parallel
    #
    #   series:
    #   Start component containers one at a time
    #
    #   seriesfirst:
    #   The first container to update to a new version must
    #   acquire a lock, but after the new version has been deployed
    #   once, all other instances may update in parallel.
    #
    #   This is useful for cases where the component performs some
    #   provisioning step that may not tolerate concurrent execution
    #   (e.g. a db schema migration, or creation of a queue).
    #   If seriesfirst is used, the first instance of a new version will
    #   run in isolation (creating the relevant resources), and then all
    #   other containers can start (which will no-op on the resource creation
    #   or schema migration)
    startparallelism: series

Logging options

By default logs go to the default Docker json logger and can be viewed via docker logs on the host running the container. In most cases you’ll want to specify a different logdriver so that logs are aggregated somewhere.

The logdriver value is passed through to Docker verbatim, with one exception. If logdriver: maelstrom is specified, output from all containers spawned by Maelstrom will be copied to maelstromd’s stdout. This is useful when doing local development, as it allows you to easily aggregate all application logs in one place.


---
name: myproject
components:
  component_name_1:
    # Docker log driver to use. Optional.
    # For all options, see:
    # https://docs.docker.com/config/containers/logging/configure/
    logdriver: syslog
    # options are specific to the log driver specified above
    # See docker documentation for details
    logdriveroptions:
      syslog-address: udp://192.168.1.30:514
      syslog-facility: local4

Docker pull options

By default docker images are pulled without authentication. Each component may optionally specify registry auth credentials or provide a completely custom command to run on the host to pull the image.

Floating tags

If you use a floating tag (e.g. “latest”) you should set pullimageonstart: true or pullimageonput: true to ensure that changes to the image are pulled. Otherwise maelstrom may continue to start containers with a stale version of the image.

Pull before each start

Images are always pulled if they do not exist locally, but are not pulled if the image already exists. If you want maelstrom to pull the image before starting any container for the component, set pullimageonstart: true.


---
name: myproject
components:
  component_name_1:
    pullimageonstart: true

Pull after component put

To pull an image immediately after a component put operation, set pullimageonput: true. This will pull the image immediately any time the component is modified, including during a project put call that modifies the component.


---
name: myproject
components:
  component_name_1:
    pullimageonput: true

Registry credentials


---
name: myproject
components:
  component_name_1:
    pullusername: scott
    pullpassword: tiger

Custom command

The <image> string will be replaced with the docker image configured on the component.


---
name: myproject
components:
  component_name_1:
    pullcommand: ["/usr/local/bin/mypull", "<image>"]

HTTP options


---
name: myproject
components:
  component_name_1:
    # Port that the component's web server binds to. Default = 8080
    httpport: 8080
    # Path to make HTTP GET to for health checks. Default = /
    httphealthcheckpath: /health_check
    # Max seconds before health check must pass during container startup.
    # If this deadline is reached before the health check passes the container is stopped.
    # Default = 60
    httpstarthealthcheckseconds: 90
    # Interval in seconds to perform health check. Default = 10
    httphealthcheckseconds: 15
    # If this number of failures is reached the container will be considered
    # non-responsive and will be restarted. Default = 1
    httphealthcheckmaxfailures: 2
    # If set, this command will be run inside the container (via docker exec)
    # before stopping and removing the container due to a health check failure.
    # This provides a hook that can be used to capture a thread dump or some other
    # state about the container that may be useful for debugging why it failed.
    # If not set, no command will be run before stopping a container that fails its health check.
    healthcheckfailedcommand: ["/bin/bash", "-c", "kill -QUIT 1"]

Other options

These are all optional properties.


---
name: myproject
components:
  component_name_1:
    # Volumes to bind into the container
    # If this is used in production make sure you have some mechanism
    # to provision the source path on each maelstrom node before starting maelstromd
    volumes:
      - # Source path on the host. Required.
        source: ${HOME}/foo
        # Target path of the volume in the container. Required.
        target: /opt/foo
        # Type of mount (bind, volume, or tmpfs). Optional. Default = bind
        type: bind
        # Whether volume is read only. Default = false
        readonly: false
      # add more volumes using a list
      - source: /some/other/path
        target: /otherpath
    # Expose ports from container to host
    # Probably only useful during local development (e.g. for debugger ports)
    # Note that if you statically bind a port here and maxinstances > 1
    # You could run into port conflicts if maelstrom tries to deploy the same
    # component twice on the same host. Use with care.
    ports:
      - 6001:6001
    # Network name to attach container to. Optional.
    # This is sometimes useful when developing locally and you wish to access
    # resources from a docker-compose stack that are placed on a non-default network.
    networkname: mynet
    #
    # DNS options - this allows you to override the DNS server to use in the container
    # See docs: https://docs.docker.com/v17.09/engine/userguide/networking/default_network/configure-dns/
    #
    # equivalent to docker run --dns
    dns:
      - 8.8.8.8
    # equivalent to docker run --dns-opt
    dnsoptions:
      - "option 1"
      - "option 2"
    # equivalent to docker run --dns-search
    dnssearch:
      - example.com
      - example.org
    # equivalent to docker run --ulimit <name>:<soft>:<hard>
    ulimits:
      - nofile=20000:30000
      - nproc=3
    # runs container with init PID 1
    # equivalent to docker run --init (default is false)
    containerinit: true
    #
    # user / hostname / domainname options
    #
    # equivalent to docker run --user
    user: jane
    # equivalent to docker run --hostname
    # NOTE: this should not be used for DNS purposes, as each instance of a component
    # will be assigned this same hostname. this field is supported in cases where
    # hostname is used for logging or other diagnostic purposes
    hostname: everest
    #
    # equivalent to docker run --domainname
    domainname: example.com
    #
    # Linux capabilities - each is a string array
    # equivalent to docker run --cap-add
    capadd:
      - cap1
      - cap2
    # equivalent to docker run --cap-drop
    capdrop:
      - cap3