Ruby, Unicorn, and environment variables

2019-06-15 14:00发布

问题:

While playing with Heroku, I found their approach of using environment variables for server-local configuration brilliant. Now, while setting up an application server of my own, I find myself wondering how hard that would be to replicate.

I'm deploying a sinatra application, riding Unicorn and Nginx. I know nginx doesn't like to play with the environment, so that one's out. I can probably put the vars somewhere in the unicorn config file, but since that's under version control with the rest of the app, it sort of defeats the purpose of having the configuration sit in the server environment. There is no reason not to keep my app-specific configuration files together with the rest of the app, as far as I'm concerned.

The third, and last (to my knowledge) option, is setting them in the spawning shell. That's where I got lost. I know that login and non-login shells use different rc files, and I'm not sure whether calling something with sudo -u http stuff is or not spawning a login shell. I did some homework, and asked google and man, but I'm still not entirely sure on how to approach it. Maybe I'm just being dumb... either way, I'd really appreciate it if someone could shed some light on the whole shell environment deal.

回答1:

I think your third possibility is on the right track. What you're missing is the idea of a wrapper script, whose only function is to set the environment and then call the main program with whatever options are required.

To make a wrapper script that can function as a control script (if prodEnv use DB=ProdDB, etc), there is one more piece that simplifies this problem. Bash/ksh both support a feature called sourcing files. This an operation that the shell provides, to open a file and execute what is in the file, just as if it was in-lined in the main script. Like #include in C and other languages.

ksh and bash will automatically source /etc/profile, /var/etc/profile.local (sometimes), $HOME/.profile. There are other filenames that will also get picked up, but in this case, you'll need to make your own env file and the explicitly load it.

As we're talking about wrapper-scripts, and you want to manage how your environment gets set up, you'll want to do the sourcing inside the wrapper script.

How do you source an environment file?

envFile=/path/to/my/envFile  
. $envFile

where envFile will be filled with statements like

dbServer=DevDBServer
webServer=QAWebServer
....

you may discover that you need to export these variable for them to be visble

export dbServer webServer

An alternate assignment/export is supported

export dbServer=DevDBServer
export webServer=QAWebServer

Depending on how non-identical your different environments are, you can have your wrapper script figure out which environment file to load.

case $( /bin/hostame ) in
 prodServerName )
     envFile=/path/2/prod/envFile ;;
 QASeverName )
     envFile=/path/2/qa/envFile ;;
 devSeverName )
     envFile=/path/2/dev/envFile ;;
esac

. ${envFile}

#NOW call your program
myProgram -v -f inFile -o outFile ......

As you develop more and more scripts in your data processing environment, you can alway source your envFile at the top. When you eventually change the physical location of a server (or it's name), then you have only one place that you need to make the change.

IHTH



回答2:

Also a couple of gems dealing with this. figaro that works both with or without heroku. Figaro uses a yaml file (in config and git ignored) to keep track of variables. Another option is dotenv that reads variables from an .env file. And also another article with all them options.



回答3:

To spawn an interactive shell (a.k.a. login shell) you need to invoke sudo like this:

sudo -i -u <user> <command>

Also you may use -E to preserve the environment. This will allow some variables to be pased for your current environment to the command invoked with sudo.



回答4:

I solved a similar problem by explicitly telling Unicorn to read a variables file as part of startup in its init.d script. First I created a file in a directory above the application root called variables. In this script I call export on all my environment variables, e.g. export VAR=value. Then I defined a variable GET_VARS=source /path/to/variables in the /etc/init.d/unicorn file. Finally, I modified the start option to read su - $USER -c "$GET_VARS && $CMD" where $CMD is the startup command and $USER is the app user. Thus, the variables defined in the file are exported into the shell of Unicorn's app user on startup. Note that I used an init.d script almost identical to the one from this article.