Managing application configuration in a chef environment cookbook - deployment

I am new to chef and have been struggling to find best practices on how to configure application configuration in an environment cookbook [source #1].
The environment cookbook I'm working on should do the following:
Prepare the node for a custom application deployment by creating directories, users, etc. that are specific for this deployment only.
Add initialization and monitoring scripts specific for the application deployment.
Define the application configuration settings.
This last responsibility has been a particularly tough nut to crack.
An example configuration file of an application deployment might look as follows:
{
"server": {
"port": 9090
},
"session": {
"proxy": false,
"expires": 100
},
"redis": [{
"port": 9031,
"host": "rds01.prd.example.com"
}, {
"port": 9031,
"host": "rds02.prd.example.com"
}],
"ldapConfig": {
"url": "ldap://example.inc:389",
"adminDn": "CN=Admin,CN=Users,DC=example,DC=inc",
"adminUsername": "user",
"adminPassword": "secret",
"searchBase": "OU=BigCustomer,OU=customers,DC=example,DC=inc",
"searchFilter": "(example=*)"
},
"log4js": {
"appenders": [
{
"category": "[all]",
"type": "file",
"filename": "./logs/myapp.log"
}
],
"levels": {
"[all]": "ERROR"
}
},
"otherService": {
"basePath" : "http://api.prd.example.com:1234/otherService",
"smokeTestVariable" : "testVar"
}
}
Some parts of this deployment configuration file are more stable than others. While this may vary depending on the application and setup, things like port numbers and usernames I prefer to keep the same across environments for simplicity's sake.
Let me classify the configuration settings:
Stable properties
session
server
log4js.appenders
ldapConfig.adminUsername
ldapConfig.searchFilter
otherService.basePath
redis.port
Environment specific properties
log4js.levels
otherService.smokeTestVariable
Partial-environment specific properties
redis.host: rds01.[environment].example.com
otherService.basePath: http://api.[environment].example.com:1234/otherService
Encrypted environment specific properties
ldapConfig.adminPassword
Questions
How should I create the configuration file? Some options: 1) use a file shipped within the application deployment itself, 2) use a cookbook file template, 3) use a JSON blob as one of the attributes [source #2], 4)... other?
There is a great diversity of variability in the configuration file; how best to manage these using Chef? Roles, environments, per-node configuration, data-bags, encrypted data-bags...? Or should I opt for environment variables instead?
Some key concerns in the approach:
I would prefer there is only 1 way to set the configuration settings.
Changing the configuration file for a developer should be fairly straightforward (they are using Vagrant on their local machines before pushing to test).
The passwords must be secure.
The chef cookbook is managed within the same git repository as the sourcecode.
Some configuration settings require a great deal of flexibility; for example the log4js setting in my example config might contain many more appenders with dozens of fairly unstructured variables.
Any experiences would be much appreciated!
Sources
http://blog.vialstudios.com/the-environment-cookbook-pattern/
http://lists.opscode.com/sympa/arc/chef/2013-01/msg00392.html
http://jtimberman.housepub.org/blog/2013/01/28/local-templates-for-application-configuration/
http://realityforge.org/code/2012/11/12/reusable-cookbooks-revisited.html

Jamie Winsor gave a talk at chefconf that goes further in explaining the environment cookbook pattern's rationale and usage:
Chefcon: talking about self-contained releases, using chef
Slides
In my opinion one of the key concepts this pattern introduces is the idea of using chef environments to control the settings of each application instance. The environment is updated, using berkshelf, with the run-time version of the cookbooks being used by the application.
What is less obvious is that if you decide to reserve a chef environment for the use of a single application instance, it then it becomes safe to use that environment to configure the application's global run-time settings.
An example if given in the berkshelf-api installation instructions. There you will see production environment (for the application) being edited with various run-time settings:
knife environment edit berkshelf-api-production
In conclusion, chef gives us lots of options. I would make the following generic recommendations:
Capture defaults in the application cookbook
Create an environment for each application instance (as recommended by pattern)
Set run-time attribute over-rides in the environment
Notes:
See also the berksflow tool. Designed to make the environment cookbook pattern easier to implement.
I have made no mention of using roles. These can also be used to override attributes at run-time, but might be simpler to capture everything in a dedicated chef environment. Roles seem better suited to capturing information peculiar to a component of an application.

Related

Using environment variables to configure Docker deployment of Lagom Scala application

We're developing several Lagom-based Scala micro-services. They are configured using variable replacement in application.conf, eg.
mysql = {
url = "jdbc:mysql://"${?ENV_MYSQL_DATABASE_URL}
During development, we set these variables as Java System Properties via a env.sbt file that calls System.setProperty("ENV_MYSQL_DATABASE_URL", url). This is working fine.
Now I want to deploy this in a container to my local Docker installation. We are using the SbtReactiveAppPlugin to build the Docker image from build.sbt and simply run sbt Docker/publishLocal. This works as expected, a Docker image is created and I can fire it up.
However, passing in environment variables using the standard docker or docker-compose mechanisms does not seem to work. While I can see that the environment variables are set correctly inside the Docker container (verified using env on a bash and also by doing log.debug("ENV_MYSQL_DATABASE_URL via env: " + sys.env("ENV_MYSQL_DATABASE_URL")) inside the service), they are not used by the application.conf and not available in the configuration system. The values are empty/unset (verified through configuration.getString("ENV_MYSQL_DATABASE_URL").toString() and the exceptions thrown by the mysql system and other systems).
The only way I've gotten it to work was by fudging this into the JAVA_OPTS via JAVA_OPTS=-D ENV_MYSQL_DATABASE_URL=..... However, this seems like a hack, and doesn't appear to scale very well with dozens of environment parameters.
Am I missing something, is there a way to easily use the environment variables inside the Lagom application and application.conf?
Thanks!
I've used Lightbend config to configure Lagom services via environment variables in docker containers for many years, so know that it can be done and has been pretty straightforward in my experience.
With that in mind, when you say that they're not used by application.conf, do you mean that they're unset? Note that unless you're passing a very specific option as a Java property, configuration.getString("ENV_MYSQL_DATABASE_URL") will not read from an environment variable, so checking that will not tell you anything about whether mysql.url is affected by the environment variable. configuration.getString("mysql.url") will give you a better idea of what's going on.
I suspect that in fact your Docker image is being built with the dev-mode properties hardcoded in, and since Java system properties take precedence over everything else, they're shadowing the environment variable.
You may find it useful to structure your application.conf along these lines:
mysql_database_url = "..." # Some reasonable default default for dev-mode
mysql_database_url = ${?ENV_MYSQL_DATABASE_URL}
mysql {
url = "jdbc://"${mysql_database_url}
}
In this case, you have a reasonable default for a developer (probably including in the docs some instructions for running MySQL in a way compatible with that configuration). The default can then be overridden via setting a Java property (e.g. JAVA_OPTS=-Dmysql_database_url) or by setting the ENV_MYSQL_DATABASE_URL environment variable.
While I agree with the answer provided by Levi Ramsey, I would suggest you to use typesafe's config to load the your config

How to Set IP to Static with Powershell and Azure

I have an Azure Dev Test Lab that I am deploying to Azure via Power Shell. I am able to deploy the ARM templates and join to the test domain (not Azure AD) with no issues. The next step I would like to do is to set the IP to static. I can think of 3 ways to possibly do this. Either figure out the IP structure beforehand and deploy it with those settings. Let the DHCP assign the settings and try to problematically set them from Dynamic to Static using Powershell DSC. Or some type of preferred lease from the DHCP. These labs are meant to be stood up and torn down ad hoc. The IPs are internal and not Public. It is possible for me to know the IPs before hand. Could someone make a recommendation on what would make the most sense to pursue?
Well, there are several ways of looking at it, first of all, you can define ip at deployment time, by setting it to static, instead of dynamic:
{
"name": "xxx",
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2016-10-01",
"location": "loc",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Static",
"privateIPAddress": "ipgoeshere",
"subnet": {
"id": "subnetgoeshere"
}
}
}
]
}
but this method is only valid if you know the available IP addresses beforehand and you will have to look those up and pass to the template.
Another way of doing this is créating NIC as dynamic, getting its IP address and setting it to static. All can be done with an ARM Template. The example is a bit too much to paste here, you can check it here. look for deployments called: "[concat(variables('vmNamePrefix'),'setStaticIp')]", and "[concat(variables('vmNamePrefix'),copyIndex(1),'-primaryIp')]", and their corresponding templates: getip and setip
You can do pretty much the same with powershell, I dont have a script Handy, but the logic is the same, deploy > getip > setip

IBM API Connect - Can custom connectors be exposed via UI?

In SLC ARC the list of connectors available (when creating datasources and thus generating models) via the UI was hard-coded (link to overview of issue) Does the same hold true for API Connect?
Effectively, I'd like to create a fork of the mssql connector to address some issues with how schemas are processed when generating models from existing tables. If I create such a connector, will I be able to install it so that I can utilize it via the GUI (again, I could not via SLC ARC due to hard-coding). Any help is greatly appreciated!
EDIT: I've installed the loopback-connector-redis connector into a throwaway project. When I spin up APIC it does not appear on the data sources screen. So, rephrasing my question: are there settings or otherwise that would allow such connectors to be included. Ideally, APIC would scan my project and determine what I have installed, exposing those connectors.
As you've seen, the list is currently fixed and doesn't detect additional installed connectors.
If you want to use your own custom connector, create a new datasource using the API Designer, select the MSSQL connector and fill in the values per usual.
Next, you'll need to open a file on your system to tweak the connector target.
In your project directory, open ./server/datasources.json and you should see the datasource you just created. Then, just change the connector value to the name of the custom version you created, save, and continue developing your APIs like normal.
{
"db": {
"name": "db",
"connector": "memory"
},
"DB2 Customers": {
"host": "datbase.acme-air.com",
"port": 50000,
"database": "customers",
"password": "",
"name": "Customer DB",
"connector": "db2-custom",
"user": "mhamann#us.ibm.com"
}
}
Unfortunately, you're now on your own in terms of managing datasources, as they won't show up in the Designer's datasource editor. They will still be usable in other parts of the Designer, so you can connect up your models, etc.

Setting :deploy_to from server config in Capistrano3

In my Capistrano 3 deployment, I would like to set the set :deploy_to, -> { "/srv/www/#{fetch(:application)}" } so the :deploy_to is different for each server it deploys to.
In my staging.rb file I have:
server 'dev.myserver.com', user: 'deploy', roles: %w{web app db}, install_path: 'mycustom/path'
server 'dev.myserver2.com', user: 'deploy', roles: %w{web app db}, install_path: 'mycustom/other/path'
My question is: would it possible to use the "install_path" I defined, in my :deploy_to? If that's possible, how would you do it?
Finally, after looking around, I came onto an issue from one of the developer of Capistrano, stating specifically that it can't be done
Quote from the Github issue:
Not possible, sorry. fetch() (as is documented widely) reads values
set by set(), the only reason to use set() and fetch() over regular
ruby variables is to provide a consistent API between plugins and
extensions, and because set() can take a Proc to be resolved later.
The variables you are setting in the host object via the server()
command belong to an individual host, some of them, user, roles, etc
have special meanings. For more information see
https://github.com/capistrano/sshkit/blob/master/EXAMPLES.md#do-something-different-on-one-host-or-another-depending-on-a-host-property.
If you specifically need to deploy to a different directory on each
machine you probably should not be using the built-in tasks (they
don't fit your needs), and rather copy the deploy.rake from the Gem
into your own project, and modify it as you need. Which in this case
might be to not take fetch(:deploy_to), but to read that from a host
property.
You could try to do something where before doing anything that relies
on calling fetch(:deploy_to), you set() it using the value from
host.someproperty but I'm pretty sure that'll break in exciting and
interesting ways.

What's the best Perl module for hierarchical and inheritable configuration?

If I have a greenfield project, what is the best practice Perl based configuration module to use?
There will be a Catalyst app and some command line scripts. They should share the same configuration.
Some features I think I want ...
Hierarchical Configurations to cleanly maintain different development and live settings.
I'd like to define "global" configurations once (eg, results_per_page => 20), have those inherited but override-able by my dev/live configs.
Global:
results_per_page: 20
db_dsn: DBI:mysql;
db_name: my_app
Dev:
inherit_from: Global
db_user: dev
db_pass: dev
Dev_New_Feature_Branch:
inherit_from: Dev
db_name: my_app_new_feature
Live:
inherit_from: Global
db_user: live
db_pass: secure
When I deploy a project to a new server, or branch/fork/copy it somewhere new (eg, a new development instance), I want to (one time only) set which configuration set/file to use, and then all future updates are automatic.
I'd envisage this could be achieved with a symlink:
git clone example.com:/var/git/my_project . # or any equiv vcs
cd my_project/etc
ln -s live.config to_use.config
Then in the future
git pull # or any equiv vcs
I'd also like something that akin to FindBin, so that my configs can either use absolute paths, or relative to the current deployment. Given
/home/me/development/project/
bin
lib
etc/config
where /home/me/development/project/etc/config contains:
tmpl_dir: templates/
when my perl code looks up the tmpl_dir configuration it'll get:
/home/me/development/project/templates/
But on the live deployment:
/var/www/project/
bin
lib
etc/config
The same code would magically return
/var/www/project/templates/
Absolute values in the config should be honoured, so that:
apache_config: /etc/apache2/httpd.conf
would return "/etc/apache2/httpd.conf" in all cases.
Rather than a FindBin style approach, an alternative might be to allow configuration values to be defined in terms of other configuration values?
tmpl_dir: $base_dir/templates
I'd also like a pony ;)
Catalyst::Plugin::ConfigLoader supports multiple overriding config files. If your Catalyst app is called MyApp, then it has three levels of override: 1) MyApp.pm can have a __PACKAGE__->config(...) directive, 2) it next looks for MyApp.yml in the main directory of the app, 3) it looks for MyApp_local.yml. Each level may override settings in each other level.
In a Catalyst app I built, I put all of my immutable settings in MyApp.pm, my debug settings in MyApp.yml, and my production settings in MyApp_<servertype>.yml and then symlinked MyApp_local.yml to point at MyApp_<servertype>.yml on each deployed server (they were all a little different...).
That way, all of my config was in SVN and I just needed one ln -s step to manually config a server.
Perl Best Practices warns against exactly what you want. It states that config files should be simple and avoid the sort of baroque features you desire. It goes on to recommend three modules (none of which are Core Perl): Config::General, Config::Std, and Config::Tiny.
The general rational behind this is that the editing of config files tends to be done by non-programmers and the more complicated you make your config files, the more likely they will screw them up.
All of that said, you might take a look at YAML. It provides a full featured, human readable*, serialization format. I believe the currently recommend parser in Perl is YAML::XS. If you do go this route I would suggest writing a configuration tool for end users to use instead of having them edit the files directly.
ETA: Based on Chris Dolan's answer it sounds like YAML is the way to go for you since Catalyst is already using it (.yml is the de facto extension for YAML files).
* I have heard complaints that blind people may have difficulty with it
YAML is hateful for config - it's not non-programmer friendly partly because yaml in pod is by definition broken as they're both white-space dependent in different ways. This addresses the main problem with Config::General. I've written some quite complicated config files with C::G in the past and it really keeps out of your way in terms of syntax requirements etc. Other than that, Chris' advice seems on the money.