An example project that could be deployed with ocdeployer
, along with explanations for how this configuration is set up.
Note that this is just deploying a few random example templates for the sake of teaching a lesson :)
Suppose we have a project that consists of two groups of services:
- set1 -- which has nginx and a postgres DB
- set2 -- which has a ruby "hello-world" app, and a MySQL DB
set2
relies on set1
as a dependency before it can be deployed. In addition, we want to be able to re-use the same templates to deploy to two different environments (QA & Prod). In addition, we have a few "special things" we want to do after deploying set2
-- in other words, simply running oc process
on the templates, oc apply
to install them, and waiting for their DeploymentConfig
to switch to active
isn't enough. We have some extra things to do.
The project structure will be as follows:
├── env
│ ├── prod-env.yml
│ └── qa-env.yml
├── secrets
│ ├── mysql-secrets.yml
│ └── postgres-secrets.yml
└── templates
├── _cfg.yml
├── set1
│ ├── _cfg.yml
│ ├── env
│ │ └── qa-env.yml
│ ├── nginx.yml
│ └── postgres.yml
└── set2
├── _cfg.yml
├── custom
│ └── deploy.py
├── mysql.yml
└── ruby-app.yml
-
In our
templates
directory, we create a_cfg.yml
to define the order in which each set should be deployed. We could deployset1
andset2
in the same stage, but remember thatset2
depends onset1
being deployed first, so we'll use two separate stages here.A
stage
denotes a boundary -- we won't move on to the next stage until all components in the current stage are ready. Ifreturn_immediately
is set toTrue
, however, we won't wait for the deployments to reachactive
state and we'll move right on to the next stage.deploy_order: # Defines the order components should be deployed in. # If you specify components with "-c", only those components will be deployed, but # the order in which they are deployed will be preserved. stage0: return_immediately: False # You can optionally define the timeout on a stage. We'll wait <timeout> sec for deployments to become active before timing out. # The default is 300 sec #timeout: 400 components: - "set1" stage1: return_immediately: False components: - "set2"
-
In the
templates
directory, we create two folders to represent each service set:templates/set1
andtemplates/set2
. We'll put the OpenShift YAML templates files into their appropriate directory:nginx.yml
andpostgres.yml
go inset1
,mysql.yml
andruby-app.yml
go inset2
. In addition, each service set folder gets its own_cfg.yml
to define how that service set is deployed.templates/set1/_cfg.yml
looks like this:images: # Images our configs rely on. We will run 'oc import-image' on these. # The key is the ImageStream name. The value is the docker image to pull. - nginx: "nginx" - postgresql: "postgresql" secrets: # Names of secrets these templates rely on that we need to import - "postgres-dbsecrets" deploy_order: stage0: components: # We'll deploy both components in stage zero - "mysql" - "postgres"
templates/set2/_cfg.yml
looks like this:requires: # This set cannot be deployed until 'set1' is deployed - "set1" images: # Images our configs rely on. We will run 'oc import-image' on these. # The key is the ImageStream name. The value is the docker image to pull. - origin-custom-docker-builder: "openshift/origin-custom-docker-builder" - mysql-57-centos7: "centos/mysql-57-centos7" # Indicates we are making use of a custom pre-deploy/deploy/post-deploy script custom_deploy_logic: True secrets: # Names of secrets these templates rely on that we need to import - "mysql-dbsecret" deploy_order: # We will deploy mysql in the first stage. # Once it switches to 'active', we will move on to deploy ruby-app stage0: components: - "mysql" stage1: components: - "ruby-app"
-
set2
contains custom deploy logic, intemplates/set2/custom/deploy.py
. There is apost_deploy
method defined in there to do some "extra work" after the deploy has occurred. As an example, in this script we patch aConfigMap
with updated info after thenginx1
service has been deployed and we are able to see what the frontend's auto-generated route is.import json import time from ocdeployer.common.utils import oc, wait_for_ready, get_json, get_routes, rollout def post_deploy(**kwargs): map_name = "nginx-index-html" deployment_name = "nginx1" configmap = get_json("configmap", map_name) api_route = get_routes()[deployment_name] current = configmap["data"]["index.html"] configmap["data"]["index.html"] = current.replace("{{ROUTE}}", api_route) oc("patch", "configmap", map_name, p=json.dumps(configmap), _exit_on_err=False) rollout(deployment_name)
-
The secrets these templates rely on which are too sensitive to store in the template data itself are kept in a yaml file (or copied into it at deploy runtime) in the
secrets
directory. See mysql-secrets.yml and postgres-secrets.yml. -
We define two environment files in the
root
env dir:
- prod-env.yml -- defines the variables that apply when deploying these services to production
- qa-env.yml -- defines the variables that apply when deploying these services to a QA env
- For the sake of the example, we also define a service-set environment file at
templates/set1/env/qa-env.yml
. This env file overrides a couple options that will be specific to only service set 1 in the qa env.
Now, we can do the following:
Deploy only set1 to project 'myproject' using QA env settings
$ ocdeployer deploy -s set1 -e qa-env myproject
Deploy all service sets to project 'myproject' using production env settings
$ ocdeployer deploy -a -e prod-env myproject
If we had more sets, you could deploy only set1 and set2 with the below command. Note that even though we have listed set2
first, it will still get deployed in the order the service sets are listed in _cfg.yml
$ ocdeployer deploy -s set2,set1 -e prod-env myproject
You can use -p
to pick single components like so:
$ ocdeployer deploy -p set1/nginx -e prod-env myproject
You can also use a comma,separated,list with -p
to pick multiple specific components. You can combine -s
and -p
to deploy a mixture of entire service sets, plus single components.
ocdeployer
essentially does the following for each service set as it deploys them:
- Runs
oc import-image
for any images listed in the_cfg.yml
. NOTE: if image streams with the same name/tag already exist in the project, they will be re-imported. - Imports any needed secrets from the secrets local dir (if
--secrets-local-dir
is specified), or from a separate OpenShift project (if--secrets-src-project
is specified). NOTE: if any secrets exist with the same name in the project, they will be overwritten. - Runs custom pre-deploy logic, if any is defined.
- For each stage, it deploys the components in the configured order. If the default
deploy
logic is not overwritten by a custom deploy method:- the service-set env file is merged into the root-level env file.
- using the env settings, the template is run through jinja2 processing first. jinja2 processing will use any values in the env config for each component that are not defined as 'parameters'
- the template is then processed via
oc process -f
and theparameters
defined on components in the env file are passed in. If a parameter exists in the env file but it is NOT defined in the template, it will not be passed in. oc apply
is run for the processed template. This means you can "re-deploy" over an existing deployment, and if items already exist, the config for them is just overwritten.
- Waits for any
BuildConfigs
that were just deployed to build successfully, andDeploymentConfigs
that were just configured to reach "active" state, then moves on to the next stage. NOTE it waits for all components in parallel - When all stages are completed in the service set, it runs custom post-deploy logic for the service set, if any is defined.