Using Runtime Templates

Apcera supports the use of runtime templates.

A template is a flat file that exposes information about the container runtime environment to applications or jobs running inside the container. Jobs and applications may need access to this information to complete their configuration when running within a container. Templates enable you to configure your app from Apcera job environments and are usually used with frameworks that expect configuration files to reside on disk (for example, the Rails database.yml file).

You can use templates with any app or job. To template app values, you specify a template file in the app manifest. To template values in a job other than an app, you can specify them in a package script by file path or by calling the stager API. At runtime the Apcera templating engine evaluates the template attributes and replaces them with actual values.

See also the templating tutorial.

Template Parameters

The following table lists and describes the available template parameters. Refer to the example that follows this table for example template implementation.

Parameter Description
uuid Returns the UUID of the related job. Useful for correlating logs from each instance of a application.
name Return the application's name, assigned by the creator of the application.
num_instances Returns the total number of instances of this application that are running.
cpu Returns the CPU resources requested for this application.
memory Returns the bytes of memory requested for this application.
disk Returns the bytes of disk storage requested for this application.
tags Returns the list of tags applied to this application. These should be accessed via iteration. For example, to output one tag per line:
{{range tags}}
tag: {{.}}
{{end}}
has_tag Returns a boolean indicating if a tag named "name" is applied to this job, for example:
{{has_tag "production"}}
production config...
{{ end }}
routes Returns an array of routes. Accessing the route fields in the template is done as follows:
{{range routes}}
address={{.addr}}
public={{.Public}}
{{end}}
Use 'address' to deliver IP:port. These are generated from all of the routes on all of the job ports.
bindings Returns bindings to range over in the template. Accessing fields of the binding is done as follows:
{{range bindings}}
name: {{.Name}}
pipeline_config: {{.PipelineCfg}}
service: {{.Service}}
provider: {{.Provider}}
uri: {{.URI}}
{{end}}
Individual fields of the binding URI can also be accessed as follows:
scheme: {{.URI.Scheme}}
user: {{.URI.User}}
password: {{.URI.Password}}
host: {{.URI.Host}}
port: {{.URI.Port}}
path: {{.URI.Path}}
trimmedpath: {{.URI.TrimmedPath}} – same as Path, but without leading /. Useful for DB names
raw_query: {{.URI.RawQuery}
bindings_by_scheme Find all bindings with a specific scheme.
binding Returns the first binding found by the specified name. For example:
{{with binding_service "my-binding"}}
{{.URI}}
{{end}}
binding_provider Returns the first binding found by the specified provider name. For example:
{{with binding_service "postgress-provider"}}
{{.URI}}
{{end}})
binding_service Returns the first binding found by the specified service name. For example:
{{with binding_service "my-postgres"}}
{{.URI}}
{{end}}`
env_map.<KEY> Map that exposes job process environment variables. For example: {{env_map.PORT}}. Note that the PWD and OLDPWD environment variables are not exposed by this map.

Template Example

The following table shows the input (before) and output (after) of a template file evaluation:

Input Output
scalars:
uuid: {{uuid}}
name: {{name}}
num_instances: {{num_instances}}
cpu: {{cpu}}
memory: {{memory}}
disk: {{disk}}

tags:
{{range tags}}
  {{.Name}}: {{.Value}}
{{end}}

routes:
{{range routes}}
  URL: {{.URL}}
{{if .Public}}
  Port: {{.ListenPort}}
{{else}}
  UpdatePort: {{.ListenPort}}
{{end}}
{{end}}

bindings:
{{range bindings}}
  Name: {{.Name}}
  Provider: {{.Provider}}
  Service: {{.Service}}
  URI: {{.URI}}
    scheme={{.URI.Scheme}}
    user={{.URI.User}}
    password={{.URI.Password}}
    host={{.URI.Host}}
    port={{.URI.Port}}
    path={{.URI.Path}}
    raw_query={{.URI.RawQuery}}
{{end}}











scalars:
uuid: 98086f73-544e-4650-9a49-db3ca53b57dd
name: job::/sandbox/captain_jack::example-ruby-manifest
num_instances: 1
cpu: 0
memory: 268435456
disk: 805306368

tags:

  app: example-ruby-manifest


routes:

  URL: http://example-ruby-manifest.vagrant.apcera.net



  UpdatePort: 0



bindings:

  Name: binding::/sandbox/captain_jack::example-ruby-manifest_mydb1/_BACKEND
  Provider: provider::/sandbox/captain_jack::postgres
  Service: service::/sandbox/captain_jack::mydb1
  URI: postgres://hl6hulqakqhmeq4g:4JjtAuYFFtXT5Hkx@10.0.167.167/e0308bca5477424d92b14c58f6bccc39
    scheme=postgres
    user=hl6hulqakqhmeq4g
    password=4JjtAuYFFtXT5Hkx
    host=10.0.167.167
    port=0
    path=/e0308bca5477424d92b14c58f6bccc39
    raw_query=

  Name: binding::/sandbox/captain_jack::example-ruby-manifest_mydb2/_BACKEND
  Provider: provider::/sandbox/captain_jack::postgres
  Service: service::/sandbox/captain_jack::mydb2
  URI: postgres://14kg6fkuhmgomjet:kg2l7Uq330sonWpm@10.0.167.167/22a25ecb346b4d749a12ce45653502a2
    scheme=postgres
    user=14kg6fkuhmgomjet
    password=kg2l7Uq330sonWpm
    host=10.0.167.167
    port=0
    path=/22a25ecb346b4d749a12ce45653502a2
    raw_query=

Using Templates with App Manifests

The templates manifest attribute is a list of file paths that will be evaluated as templates.

For example, the following manifest snippet references the app.rb template file:

templates: [
  {
    path: "app.rb"
  },
]

The app.rb template file contains parameters whose values will be populated at runtime by Apcera, for example:

scalars:
uuid: {{uuid}}
name: {{name}}
num_instances: {{num_instances}}
cpu: {{cpu}}
memory: {{memory}}
disk: {{disk}}

Refer to "Working with Manifests" for more information on manifest files and using the template option.

Note that there may be cases when an app would benefit from templating, but the necessary files are hidden in an archive or other package. A pre-packaged Java WAR file that is deployed directly by Tomcat is an example of this situation. In these cases, the following options are available:

  • Configure the app to reference variable definitions from a file outside of the archive
  • Reference an environmental variable that can be changed on demand

Using Templates with Packages for Jobs

In addition to manifests for app creation and deployment, you can also use templates with any other type of job using Apcera's package definition framework. To do this you have two options:

Option 1) Add the /TEMPLATES file path to the package.conf file (or whatever name you have given to the package definition file)

Option 2) Do a PUT call to the Stager API from the package.conf with the path to the template file.

An example for each option is provided below. The custom package in this example involves the injection of App Dynamics into an application.

The template value delimiter for the file path option must be curly braces ({}). For the API call you can specify the delimiter similar to the way you can with mainifests.

Using a template by direct file reference

The following example demonstrates how to use templated variables for a job package using a reference to the file path.

more appDynamics.conf
name:      "appDynamics"
version:   "4.0.1.0"
namespace: "/apcera/pkg/packages"

build_depends [ { package: "build-essential" } ]
depends       [ { os: "linux" }  ]
provides      [ { package: "appDynamics" } ]

environment { "PATH": "/opt/apcera/apache-tomcat-6.0.43/bin:$PATH",
              "CATALINA_HOME": "/opt/apcera/apache-tomcat-6.0.43",
              "CATALINA_OPTS": "$CATALINA_OPTS -javaagent:/opt/appdynamics/appagent/javaagent.jar"  }

build (
  sudo wget https://www.dropbox.com/s/snjdjd117de66zf/AppServerAgent-4.0.1.0.tar?dl=1
  export INSTALLPATH=/opt/appdynamics/appagent
  sudo mkdir -p ${INSTALLPATH}
  #sudo unzip 'AppServerAgent-4.0.1.0.zip?dl=1' -d ad
  sudo mv 'AppServerAgent-4.0.1.0.tar?dl=1' AppServerAgent-4.0.1.0.tar
  sudo tar -xvf AppServerAgent-4.0.1.0.tar
  sudo cp -a AppServerAgent-4.0.1.0/. ${INSTALLPATH}
  sudo cp /opt/appdynamics/appagent/ver4.0.1.0/conf/controller-info.xml /opt/appdynamics/appagent/conf/controller-info.xml
  sudo mkdir /opt/appdynamics/appagent/ver4.0.1.0/logs
  sudo chmod a+rw /opt/appdynamics/appagent/ver4.0.1.0/logs
  sudo chmod a+rw /opt/appdynamics/appagent/ver4.0.1.0/conf
  sudo chmod a+rw /opt/appdynamics/appagent/ver4.0.1.0/conf/controller-info.xml
  sudo chmod a+rw /opt/appdynamics/appagent/conf/controller-info.xml
  sudo chmod a+rw /opt/appdynamics/appagent/conf
  sudo touch /TEMPLATES
  sudo chmod a+rw /TEMPLATES
  sudo echo "/opt/appdynamics/appagent/conf/controller-info.xml" > /TEMPLATES
  sudo echo "/opt/appdynamics/appagent/ver4.0.1.0/conf/controller-info.xml" >> /TEMPLATES
)

In the example, the controller-info.xml file uses runtime template parameters to set some values. The first file path creates (>) the /TEMPLATES file, the second appends (>>) to the file. The resulting /TEMPLATES file that is created contains those two file paths:

/opt/appdynamics/appagent/conf/controller-info.xml
/opt/appdynamics/appagent/ver4.0.1.0/conf/controller-info.xml

These XML files contain templated variables that get populated at runtime. The TEMPLATES file must be at the root level (/TEMPLATES).

The other commands you see involving TEMPLATES are for permissions and may not be applicable to your environment.

Using a template with a Stager API call

The following example demonstrates how to use templated variables for a job package using a call to the Stager API.

more appDynamics.conf
name:      "appDynamics"
version:   "4.0.1.0"
namespace: "/apcera/pkg/packages"

build_depends [ { package: "build-essential" } ]
depends       [ { os: "linux" }  ]
provides      [ { package: "appDynamics" } ]

environment { "PATH": "/opt/apcera/apache-tomcat-6.0.43/bin:$PATH",
              "CATALINA_HOME": "/opt/apcera/apache-tomcat-6.0.43",
              "CATALINA_OPTS": "$CATALINA_OPTS -javaagent:/opt/appdynamics/appagent/javaagent.jar"  }

build (
  sudo wget https://www.dropbox.com/s/snjdjd117de66zf/AppServerAgent-4.0.1.0.tar?dl=1
  export INSTALLPATH=/opt/appdynamics/appagent
  sudo mkdir -p ${INSTALLPATH}
  #sudo unzip 'AppServerAgent-4.0.1.0.zip?dl=1' -d ad
  sudo mv 'AppServerAgent-4.0.1.0.tar?dl=1' AppServerAgent-4.0.1.0.tar
  sudo tar -xvf AppServerAgent-4.0.1.0.tar
  sudo cp -a AppServerAgent-4.0.1.0/. ${INSTALLPATH}
  sudo cp /opt/appdynamics/appagent/ver4.0.1.0/conf/controller-info.xml /opt/appdynamics/appagent/conf/controller-info.xml
  sudo mkdir /opt/appdynamics/appagent/ver4.0.1.0/logs
  sudo chmod a+rw /opt/appdynamics/appagent/ver4.0.1.0/logs
  sudo chmod a+rw /opt/appdynamics/appagent/ver4.0.1.0/conf
  sudo chmod a+rw /opt/appdynamics/appagent/ver4.0.1.0/conf/controller-info.xml
  sudo chmod a+rw /opt/appdynamics/appagent/conf/controller-info.xml
  sudo chmod a+rw /opt/appdynamics/appagent/conf
  curl -X PUT -F resource=templates -F action=add -F path=/path/to -F left_delimiter=\{ right_delimiter=\} $STAGER_URL/meta
)

Using a template in an environment variable

The following example demonstrates how to assign environment variables using templates. This example assumes there is an available MySQL provider that can be used.

1) Create the MySQL service:

apc service create mysql-service -p mysql-provider

2) Create Wordpress job:

apc docker pull wordpress -i wordpress \
-e 'WORDPRESS_DB_HOST={{ (binding_service "mysql-service").URI.Host }}:{{ (binding_service "mysql-service").URI.Port }}' \
-e 'WORDPRESS_DB_USER={{ (binding_service "mysql-service").URI.User }}' \
-e 'WORDPRESS_DB_PASSWORD={{ (binding_service "mysql-service").URI.Password }}' \
-e 'WORDPRESS_DB_NAME={{ (binding_service "mysql-service").URI.TrimmedPath }}' \
--routes http://mywp.<<domain.suffix>> \
--port 80 \
--ignore-volumes

Starting with Apcera Platform release 2.2, the apc docker create command is deprecated in favor of apc docker pull.

Bind the Wordpress job to MySQL service:

apc service bind mysql-service -j wordpress
apc app start wordpress

Using Templates with Docker Jobs

This section describes how to use the /TEMPLATES feature with Docker images. Although the process is similar to the way you do it for app packages, there is an important difference to consider.

A Docker image comprises layers. In fact, each command in a Docker file (ADD, COPY, RUN, etc.) results in a new layer. Although Apcera converts the Docker image into a single package, it respects the layering. If you want to use a /TEMPLATES file with a Docker image, you must ensure that this file is in the same layer as the files it references. If the /TEMPLATES file isn't in the same layer as the files that are called by the /TEMPLATES file, the Docker job package fails with a message similar to the following:

[system-error][bf2af623] Error parsing file "/var/lib/continuum/package_cache/sha256-4f5b0fc9c2a8f474ab6ffb3b0d6d7a53f6b7778bc0f3648341b20b217584cae9/app/index.html": open /var/lib/continuum/package_cache/sha256-4f5b0fc9c2a8f474ab6ffb3b0d6d7a53f6b7778bc0f3648341b20b217584cae9/app/index.html: no such file or directory
Error: Instances have exited with errors and/or there are no more restart attempts left.

The workaround is to force the files to be compiled in the same layer. To do this we use a single command for the /TEMPLATES declaration and each of the files in it are "modified" (touched). The result is that the /TEMPLATES file and the index.html file will be in the same layer.

For example, here is a Dockerfile that copies the contents of the local folder /usr/share/nginx/html to the Docker image. This folder contains the file index.html that has the template values in it.

FROM alpine:latest

RUN apk --update add nginx

COPY 2048 /usr/share/nginx/html

RUN mkdir -p /run/nginx/ && \
    chmod a+w /run/nginx/ && \
    mkdir -p /usr/share/nginx/logs && \
    chmod a+w /usr/share/nginx/logs

RUN cp /usr/share/nginx/html/TEMPLATES / && \
    for file in $(cat /TEMPLATES); do touch "$file"; done

EXPOSE 80

CMD ["nginx", "-p", "/usr/share/nginx", "-g", "daemon off;"]