How to Build an NGINX Server and Deploy a Python Flask App Using Chef
In this post we show how you can use Chef to build an Ubuntu 14.04 LTS web server running Nginx, Python 2, virtualenv, uWSGI, and Flask - all done on Windows 10 host.
All commands are executed in PowerShell on a Windows workstation. The Chef version that is used is 12.5.1 - it is the version that comes with ChefDK 0.10.0. To avoid unexpected behavior, we recommend using those versions, when following this step-by-step guide.
Before You Begin
Test Flask Application
The test application that we use for this deployment is available in the my_flask_app repository. The application was created using Visual Studio 2015 with Python Tools for Visual Studio (PTVS).
Install Chocolatey
If you do not have Chocolatey, you can install it by following the instructions on chocolatey.org. You may also see this post for instructions on how to set the Chocolatey cache location.
Install Chef Development Kit (ChefDK)
In PowerShell as Administrator:
Find your Chef version:
chef --version
Chef Development Kit Version: 0.10.0
chef-client version: 12.5.1
berks version: 4.0.1
kitchen version: 1.4.2
Install VirtualBox and Vagrant
To install VirtualBox and Vagrant, follow the steps described in Ubuntu with Vagrant and VirtualBox on Windows.
Install Vagrant Plugins
vagrant plugin install vagrant-omnibus
vagrant plugin install vagrant-berkshelf
vagrant plugin install vagrant-hostmanager
vagrant plugin install vagrant-cachier
Give your user Modify
access to WINDIR%\System32\drivers\etc\hosts
This is needed by the vagrant-hostmanager Vagrant plugin. In PowerShell as Administrator:
$acl = Get-Acl -Path $env:SystemRoot\System32\drivers\etc\hosts
$ar = New-Object System.Security.AccessControl.FileSystemAccessRule($env:username, 'Modify', 'None', 'None', 'Allow')
$acl.SetAccessRule($ar)
Set-Acl -Path $env:SystemRoot\System32\drivers\etc\hosts -AclObject $acl
Create Chef Cookbook
This directory will become the root of your source repository:
Ensure the apt cache is up to date
Reference the apt
Cookbook
Add this line to metadata.rb
:
To get the latest version string, run knife cookbook site show apt
:
chef exec knife cookbook site show apt | grep latest_version
latest_version: https://supermarket.chef.io/api/v1/cookbooks/apt/versions/2.9.2
Here is the complete file:
name 'my_flask_server'
maintainer 'The Authors'
maintainer_email 'you@example.com'
license 'all_rights'
description 'Installs/Configures my_flask_server'
long_description 'Installs/Configures my_flask_server'
version '0.1.0'
depends 'apt', '~> 2.9.2'
Set the apt cookbook's default recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
Prepare for Test Driven Development
Add .rspec
file
Paste this in the .rspec
file:
Add .kitchen.vagrant_cachier.rb
file
This file will be merged within Test Kitchen's generated Vagrantfile. In it, we put the configuration for the vagrant-cachier
plugin
.kitchen.vagrant_hostmanager.rb
file:
# This requires vagrant-cachier plugin.
# For more information see http://fgrehm.viewdocs.io/vagrant-cachier/
#
Vagrant.configure("2") do |config|
if Vagrant.has_plugin?("vagrant-cachier")
config.cache.auto_detect = true
config.cache.scope = :box
end
if Vagrant.has_plugin?("vagrant-omnibus")
config.omnibus.cache_packages = true
config.omnibus.chef_version = "12.5.1"
end
config.vbguest.auto_update = false
end
Add .kitchen.vagrant_hostmanager.rb
file
This file will be merged within Test Kitchen's generated Vagrantfile. In it, we put the configuration for the vagrant-hostmanager
plugin, which updates C:/Windows/System32/drivers/etc/hosts
, and allows you to access the virtual machine by name instead of IP address, e.g. default-ubuntu-1404
.
Paste this in the .kitchen.vagrant_hostmanager.rb
file:
# This requires vagrant-hostmanager plugin.
# For more information see https://github.com/smdahlen/vagrant-hostmanager
#
Vagrant.configure("2") do |config|
if Vagrant.has_plugin?("vagrant-hostmanager")
# update /ect/hosts on all running guests
config.hostmanager.enabled = true
# do not add offline guests to /etc/hosts
config.hostmanager.include_offline = false
# use the private ip address with ip_reslover, see below
config.hostmanager.ignore_private_ip = false
# custom IP resolver
# get each guest's IP address by running `hostname -I` on the guest
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if hostname = (vm.ssh_info && vm.ssh_info[:host])
`vagrant ssh -c "hostname -I"`.split()[1]
end
end
# also update host's /ect/hosts
config.hostmanager.manage_host = true
end
end
Update .kitchen.yml
Update .kitchen.yml
to include only the ubuntu-14.04
platform for now. Also add the data bag path and Chef version. This is how it should look at the end:
---
driver:
name: vagrant
require_chef_omnibus: 12.5.1
network:
- ["private_network", {type: "dhcp"}]
vagrantfiles:
- .kitchen.vagrant_cachier.rb
- .kitchen.vagrant_hostmanager.rb
provisioner:
name: chef_zero
platforms:
- name: ubuntu-14.04
suites:
- name: default
run_list:
- recipe[my_flask_server::default]
attributes:
Run All Tests Manually
Run all tests manually to verify everything works as expected.
First install cookbook dependencies:
then run the tests:
Setup a Build System
We will use Rake to automate the build and test tasks. Rake already comes preinstalled in the ChefDK.
Create a Rakefile
Paste this into the Rakefile
:
# See:
# https://github.com/chef-cookbooks/chef-server/blob/master/Rakefile
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
require 'foodcritic'
require 'kitchen'
# Style tests. Rubocop and Foodcritic
namespace :style do
desc 'Run Ruby style checks'
RuboCop::RakeTask.new(:ruby)
desc 'Run Chef style checks'
FoodCritic::Rake::LintTask.new(:chef) do |t|
t.options = {
fail_# categories: ['any']
}
end
end
desc 'Run all style checks'
task style: ['style:ruby', 'style:chef']
# Rspec and ChefSpec
desc 'Run ChefSpec examples'
RSpec::Core::RakeTask.new(:spec)
# Integration tests. Kitchen.ci
namespace :integration do
desc 'Same as `chef exec kitchen test -d=never`'
task :test do
Kitchen.logger = Kitchen.default_file_logger
Kitchen::Config.new.instances.each do |instance|
instance.test(:never)
end
end
end
# Default
task default: ['style', 'spec', 'integration:test']
Test
List Tasks
Run all tests
Create Application Directory
Add an integration test
Add this code to my_app_dir_spec.rb
:
require 'spec_helper'
describe 'my_flask_server::my_app_dir' do
# Serverspec examples can be found at
# http://serverspec.org/resource_types.html
# verify virtual environment dir
describe file('/var/www/my_flask_app/shared/.env2') do
it { should be_directory }
it { should be_owned_by 'www-data' }
it { should be_grouped_into 'www-data' }
end
# verify uwsgi dir
describe file('/var/www/my_flask_app/shared/.uwsgi') do
it { should be_directory }
it { should be_owned_by 'www-data' }
it { should be_grouped_into 'www-data' }
end
# verify ssh dir
describe file('/var/www/.ssh') do
it { should be_directory }
it { should be_owned_by 'www-data' }
it { should be_grouped_into 'www-data' }
end
# verify pip cache dir
describe file('/var/www/.cache') do
it { should be_directory }
it { should be_owned_by 'www-data' }
it { should be_grouped_into 'www-data' }
end
end
Write the my_app_dir
recipe
The first step is to create the recipe file, my_app_dir.rb
. Run the following command to generate it:
Write out recipes/my_app_dir.rb
like this:
#
# Cookbook Name:: my_flask_server
# Recipe:: my_app_dir
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
# app root directory
directory '/var/www/my_flask_app' do
action :create
recursive true
user 'www-data'
group 'www-data'
end
# app shared directory
directory '/var/www/my_flask_app/shared' do
action :create
user 'www-data'
group 'www-data'
end
# for virtualenv
directory '/var/www/my_flask_app/shared/.env2' do
action :create
user 'www-data'
group 'www-data'
end
# for uwsgi socket and uwsgi config
directory '/var/www/my_flask_app/shared/.uwsgi' do
action :create
user 'www-data'
group 'www-data'
end
# required by ssh
directory '/var/www/.ssh' do
action :create
recursive true
user 'www-data'
group 'www-data'
end
# required by pip
directory '/var/www/.cache' do
action :create
recursive true
user 'www-data'
group 'www-data'
end
Set the my_app_dir.rb
recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
include_recipe 'my_flask_server::my_app_dir'
Run all tests
Install Python
Add an integration test
Add this code to my_app_python_spec.rb
:
require 'spec_helper'
describe 'my_flask_server::my_app_python' do
# Serverspec examples can be found at
# http://serverspec.org/resource_types.html
# verify system python
describe package('python') do
it { should be_installed }
end
# verify system pip
describe command('which pip') do
its(:stdout) { should contain '/usr/local/bin/pip' }
end
# verify system setuptools
describe command('which easy_install') do
its(:stdout) { should contain '/usr/local/bin/easy_install' }
end
# verify system virtualenv
describe command('which virtualenv') do
its(:stdout) { should contain '/usr/local/bin/virtualenv' }
end
# verify my_flask_app virtual environment
# See `helpers/serverspec/type/virtualenv.rb`
describe virtualenv('/var/www/my_flask_app/shared/.env2') do
it { should be_virtualenv }
end
end
Create virtualenv
serverspec type
mkdir test/integration/helpers/serverspec/type
touch test/integration/helpers/serverspec/type/virtualenv.rb
Paste this code into virtualenv.rb
:
##############################################################################
# You can find the original code at:
# <https://github.com/jantman/serverspec-extended-types>
# Licensed under the MIT License
##############################################################################
# Serverspec
module Serverspec
# Type
module Type
# Virtualenv
class Virtualenv < Base
# Test whether this appears to be a working venv
#
# Tests performed:
# - venv_path/bin/pip executable by owner?
# - venv_path/bin/python executable by owner?
# - venv_path/bin/activate readable by owner?
# - 'export VIRTUAL_ENV' in venv_path/bin/activate?
#
# @example
# describe virtualenv('/path/to/venv') do
# it { should be_virtualenv }
# end
#
# @api public
# @return [Boolean]
def virtualenv?
pip_path = ::File.join(@name, 'bin', 'pip')
python_path = ::File.join(@name, 'bin', 'python')
act_path = ::File.join(@name, 'bin', 'activate')
cmd = "grep -q 'export VIRTUAL_ENV' #{act_path}"
@runner.check_file_is_executable(pip_path, 'owner') &&
@runner.check_file_is_executable(python_path, 'owner') &&
@runner.check_file_is_readable(act_path, 'owner') &&
@runner.run_command(cmd).exit_status.to_i == 0
end
end
# Serverspec Type wrapper method for Serverspec::Type::Virtualenv
#
# @example
# describe virtualenv('/path/to/venv') do
# # tests here
# end
#
# @param name [String] the absolute path to the virtualenv root
#
# @api public
# @return {Serverspec::Type::Virtualenv}
def virtualenv(name)
Virtualenv.new(name)
end
end
end
include Serverspec::Type
Add this line to test/integration/helpers/serverspec/spec_helper.rb
:
Reference the poise-python
cookbook
Add this line to metadata.rb
:
To get the latest version string, run knife cookbook site show nginx
:
chef exec knife cookbook site show poise-python | grep latest_version
latest_version: https://supermarket.chef.io/api/v1/cookbooks/poise-python/versions/1.2.1
Here is the complete file:
name 'my_flask_server'
maintainer 'The Authors'
maintainer_email 'you@example.com'
license 'all_rights'
description 'Installs/Configures my_flask_server'
long_description 'Installs/Configures my_flask_server'
version '0.1.0'
depends 'apt', '~> 2.9.2'
depends 'poise-python', '~> 1.2.1'
Write the my_app_python
recipe
The first step is to create the recipe file, my_app_python.rb
. Run the following command to generate it:
recipes/my_app_python.rb
like this:
#
# Cookbook Name:: my_flask_server
# Recipe:: my_python
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
# See https://supermarket.chef.io/cookbooks/poise-python
# install python 2
python_runtime 'python_2' do
version '2'
end
# create my_flask_app virtual environment
python_virtualenv 'my_flask_app_env' do
path '/var/www/my_flask_app/shared/.env2'
python 'python_2'
user 'www-data'
group 'www-data'
end
Set the my_app_python
recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
include_recipe 'my_flask_server::my_app_dir'
include_recipe 'my_flask_server::my_app_python'
Run all tests
Install SSH Key
Update .kitchen.yml
Add this code to .kitchen.yml
under provisioner
:
data_bags_path: "./data_bags"
encrypted_data_bag_secret_key_path: <%= ENV['CHEF_DATA_BAG_SECRET'] %>
Here is the complete file:
---
driver:
name: vagrant
require_chef_omnibus: 12.5.1
network:
- ["private_network", {type: "dhcp"}]
vagrantfiles:
- .kitchen.vagrant_cachier.rb
- .kitchen.vagrant_hostmanager.rb
provisioner:
name: chef_zero
data_bags_path: "./data_bags"
encrypted_data_bag_secret_key_path: <%= ENV['CHEF_DATA_BAG_SECRET'] %>
platforms:
- name: ubuntu-14.04
suites:
- name: default
run_list:
- recipe[my_flask_server::default]
attributes:
Generate a Data Bag Secret
$key = New-Object byte[](512)
$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($key)
[Convert]::ToBase64String($key) | Out-File "~/.chef/chef_data_bag_secret" -encoding "UTF8"
[array]::Clear($key, 0, $key.Length)
This key will be used to encrypt sensitive cookbook information like SSH private keys. Make sure you store a copy of the ~/.chef/chef_data_bag_secret
file in a secure location.
Create Environment Variable
$chef_secret = (rvpa '~/.chef/chef_data_bag_secret').Path
$env:CHEF_DATA_BAG_SECRET = $chef_secret
[Environment]::SetEnvironmentVariable("CHEF_DATA_BAG_SECRET", $chef_secret, "User")
Generate SSH Key
Make sure you use an empty passphrase.
If you have an existing key with a passphrase, remove the passphrase from a private key using code similar to:
Add the SSH key to Bitbucket
We will use Bitbucket to host the source code of our test application. Go ahead and fork the my_flask_app repository.
Then add the public part of the SSH key, ~/.ssh/id_my_flask_app_deploy.pub
, to your Bitbucket fork. See Use deployment keys for more details.
Create Data Bag Item
$json = @"
{
"id" : "my_flask_app",
"deploy_key" : "<key>"
}
"@
$json = $json -replace "<key>", ([io.file]::ReadAllText(".ssh/id_my_flask_app_deploy").Replace("`n", "\n") + "\n")
[io.file]::WriteAllText(".chef/my_flask_app.json", $json)
Encrypt Data Bag Item
mkdir ./data_bags/secrets
chef exec knife data bag from file secrets ~/.chef/my_flask_app.json -z --secret-file (rvpa ~/.chef/chef_data_bag_secret).Path
Create SSH Wrapper Script
Create files/default/wrap-ssh4git.sh
file:
Add this content to the files/default/wrap-ssh-4-git.sh
:
#!/bin/bash
ssh -o "StrictHostKeyChecking=no" -i "/tmp/my_flask_app/ssh/id_my_flask_app_deploy" $1 $2
IMPORTANT: Make sure the file uses Unix Line Ending (\n, LF) and UTF-8 encoding. Otherwise you may get weird errors when Chef client runs the script during deployment.
Add an integration test
Add this code to my_app_ssh_spec.rb
:
require 'spec_helper'
describe 'my_flask_server::my_app_ssh' do
# Serverspec examples can be found at
# http://serverspec.org/resource_types.html
describe file('/tmp/my_flask_app/ssh/wrap-ssh-4-git.sh') do
it { should be_file }
it { should be_mode '755' }
it { should be_owned_by 'www-data' }
it { should be_grouped_into 'www-data' }
end
describe file('/tmp/my_flask_app/ssh/id_my_flask_app_deploy') do
it { should be_file }
it { should be_mode '600' }
it { should be_owned_by 'www-data' }
it { should be_grouped_into 'www-data' }
end
end
Write the my_app_ssh
recipe
The first step is to create the recipe file, my_app_ssh.rb
. Run the following command to generate it:
Write out recipes/my_app_ssh.rb
like this:
#
# Cookbook Name:: my_flask_server
# Recipe:: my_app_ssh
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
directory '/tmp/my_flask_app/ssh' do
recursive true
user 'www-data'
group 'www-data'
end
# copy git ssh wrapper
cookbook_file '/tmp/my_flask_app/ssh/wrap-ssh-4-git.sh' do
source 'wrap-ssh-4-git.sh'
mode 0755
user 'www-data'
group 'www-data'
end
# decrypt the private ssh key
my_flask_app = Chef::EncryptedDataBagItem.load('secrets', 'my_flask_app')
if my_flask_app['deploy_key']
ruby_block 'decrypt `id_my_flask_app_deploy` private ssh key' do
block do
f = ::File.open('/tmp/my_flask_app/ssh/id_my_flask_app_deploy', 'w')
f.print(my_flask_app['deploy_key'])
f.close
end
not_if do
::File.exist?('/tmp/my_flask_app/ssh/id_my_flask_app_deploy')
end
end
# change permissions
file '/tmp/my_flask_app/ssh/id_my_flask_app_deploy' do
mode 0600
user 'www-data'
group 'www-data'
end
end
Set the my_app_ssh
recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
include_recipe 'my_flask_server::my_app_dir'
include_recipe 'my_flask_server::my_app_python'
include_recipe 'my_flask_server::my_app_ssh'
Run all tests
Deploy Application From Git
The test application is available in the my_flask_app repository on Bitbucket. Make sure you have forked it, and configured your SSH key, as explained earlier in this tutorial.
Reference the git
cookbook
Add this line to metadata.rb
:
To get the latest version string, run knife cookbook site show git
:
chef exec knife cookbook site show git | grep latest_version
latest_version: https://supermarket.chef.io/api/v1/cookbooks/git/versions/4.3.6
Here is the complete file:
name 'my_flask_server'
maintainer 'The Authors'
maintainer_email 'you@example.com'
license 'all_rights'
description 'Installs/Configures my_flask_server'
long_description 'Installs/Configures my_flask_server'
version '0.1.0'
depends 'apt', '~> 2.9.2'
depends 'poise-python', '~> 1.2.1'
depends 'git', '~> 4.3.6'
Add an integration test
Add this code to my_app_deploy_spec.rb
:
require 'spec_helper'
describe 'my_flask_server::my_app_deploy' do
# Serverspec examples can be found at
# http://serverspec.org/resource_types.html
# verify git deployment
describe file('/var/www/my_flask_app/current') do
it { should be_symlink }
end
describe file('/var/www/my_flask_app/current/app.py') do
it { should exist }
end
# verify requirements.txt has been processed
# i.e. local python packages like `Flask` have been installed
describe command('/var/www/my_flask_app/shared/.env2/bin/pip list') do
its(:stdout) { should contain 'Flask' }
its(:exit_status) { should eq 0 }
end
end
Write the my_app_deploy
recipe
The first step is to create the recipe file, my_app_deploy.rb
. Run the following command to generate it:
Write out recipes/my_app_deploy.rb
like this:
#
# Cookbook Name:: my_flask_server
# Recipe:: my_app_deploy
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'git::default'
# checkout app from git repo / master branch
# creates `releases`, and `current` directories
# links `current` to latest revision
deploy_revision '/var/www/my_flask_app' do
action :deploy
repo 'ssh://git@bitbucket.org/vkantchev/my_flask_app.git'
branch 'master'
user 'www-data'
group 'www-data'
ssh_wrapper '/tmp/my_flask_app/ssh/wrap-ssh-4-git.sh'
purge_before_symlink []
create_dirs_before_symlink []
symlinks({})
migrate false
symlink_before_migrate({})
end
# install python packages in `my_flask_app_env` via pip
# `my_flask_app_env` is defined in the `my_app_python.rb`
pip_requirements '/var/www/my_flask_app/current/requirements.txt' do
virtualenv 'my_flask_app_env'
user 'www-data'
group 'www-data'
action :install
end
Set the my_app_deploy
recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
include_recipe 'my_flask_server::my_app_dir'
include_recipe 'my_flask_server::my_app_python'
include_recipe 'my_flask_server::my_app_ssh'
include_recipe 'my_flask_server::my_app_deploy'
Run all tests
Install uWSGI
Add an integration test
Create test/integration/default/serverspec/my_app_uwsgi_spec.rb
Add this code to my_app_uwsgi_spec.rb
:
require 'spec_helper'
describe 'my_flask_server::my_app_uwsgi' do
# Serverspec examples can be found at
# http://serverspec.org/resource_types.html
# verify uWSGI package
describe command('/var/www/my_flask_app/shared/.env2/bin/pip list') do
its(:stdout) { should contain 'uWSGI' }
its(:exit_status) { should eq 0 }
end
# verify my_flask_app uWSGI serice
describe service('my_flask_app') do
it { should be_enabled }
it { should be_running }
end
end
Create uWSGI Python Config File
Create files/default/uwsgi/my_flask_app.ini
file:
Add this content to the files/default/uwsgi/my_flask_app.ini
:
# See:
# http://uwsgi-docs.readthedocs.org/en/latest/WSGIquickstart.html
# http://uwsgi-docs.readthedocs.org/en/latest/Upstart.html
# http://uwsgi-docs.readthedocs.org/en/latest/Options.html#plugin-python
[uwsgi]
# socket configuration
socket = /var/www/my_flask_app/shared/.uwsgi/my_flask_app.sock
chmod-socket = 664
vacuum = true
# process configuration
master = true
processes = 2
threads = 4
die-on-term = true
# app configuration
# virtual environment
virtualenv = /var/www/my_flask_app/shared/.env2
# call the app instance from the app.py module
chdir = /var/www/my_flask_app/current
module = app
callable = app
IMPORTANT: Make sure the file uses Unix Line Ending (\n, LF) and UTF-8 encoding. Otherwise you may get weird errors when Chef client runs the script during deployment.
Create uWSGI Service Config File
Create files/default/uwsgi/my_flask_app.conf
file:
Add this content to the files/default/uwsgi/my_flask_app.conf
:
description "uWSGI instance to serve my_flask_app"
start on runlevel [2345]
stop on runlevel [!2345]
setuid www-data
setgid www-data
script
cd /var/www/my_flask_app/shared
. .env2/bin/activate
uwsgi --ini .uwsgi/my_flask_app.ini
end script
IMPORTANT: Make sure the file uses Unix Line Ending (\n, LF) and UTF-8 encoding. Otherwise you may get weird errors when Chef client runs the script during deployment.
Write the my_app_uwsgi
recipe
The first step is to create the recipe file, my_app_uwsgi.rb
. Run the following command to generate it:
Write out recipes/my_app_uwsgi.rb
like this:
#
# Cookbook Name:: my_flask_server
# Recipe:: my_app_uwsgi
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
# Install the uwsgi package in virtual environment using pip
# See https://supermarket.chef.io/cookbooks/poise-python
# install uwsgi python package
python_package 'uwsgi' do
virtualenv 'my_flask_app_env'
user 'www-data'
group 'www-data'
end
# my_flask_app uWSGI configuration
cookbook_file '/var/www/my_flask_app/shared/.uwsgi/my_flask_app.ini' do
source 'uwsgi/my_flask_app.ini'
owner 'root'
group 'root'
mode 0644
end
# create my_flask_app uWSGI service
cookbook_file '/etc/init/my_flask_app.conf' do
source 'uwsgi/my_flask_app.conf'
owner 'root'
group 'root'
mode 0644
end
# start my_flask_app uWSGI service
service 'my_flask_app' do
provider Chef::Provider::Service::Upstart
supports status: true, restart: true, reload: true
action [:enable, :start]
end
Set the my_app_uwsgi
recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
include_recipe 'my_flask_server::my_app_dir'
include_recipe 'my_flask_server::my_app_python'
include_recipe 'my_flask_server::my_app_ssh'
include_recipe 'my_flask_server::my_app_deploy'
include_recipe 'my_flask_server::my_app_uwsgi'
Run all tests
Install Nginx
Add an integration test
Rename test/integration/default/serverspec/default_spec.rb
to test/integration/default/serverspec/my_app_nginx_spec.rb
Replace the contents of my_app_nginx_spec.rb
with this code:
require 'spec_helper'
describe 'my_flask_server::my_app_nginx' do
# Serverspec examples can be found at
# http://serverspec.org/resource_types.html
# verify nginx package
describe package('nginx') do
it { should be_installed }
end
# verify nginx serice
describe service('nginx') do
it { should be_enabled }
it { should be_running }
end
end
Create Nginx Site File
Create files/default/nginx/my_flask_app
file:
Add this content to the files/default/nginx/my_flask_app
:
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
# Make site accessible from http://localhost/
server_name localhost;
location / {
include uwsgi_params;
uwsgi_pass unix:/var/www/my_flask_app/shared/.uwsgi/my_flask_app.sock;
}
}
IMPORTANT: Make sure the file uses Unix Line Ending (\n, LF) and UTF-8 encoding. Otherwise you may get weird errors when Chef client runs the script during deployment.
Write the my_app_nginx
recipe
The first step is to create the recipe file, my_app_nginx.rb
. Run the following command to generate it:
Write out recipes/my_app_nginx.rb
like this:
#
# Cookbook Name:: my_flask_server
# Recipe:: my_nginx
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
# install nginx
package 'nginx' do
:upgrade
end
# start nginx service
service 'nginx' do
supports status: true, restart: true, reload: true
action [:enable, :start]
end
# Uncomment to use a custom nginx.conf
# cookbook_file '/etc/nginx/nginx.conf' do
# source 'nginx/nginx.conf'
# mode 0640
# owner 'root'
# group 'root'
# notifies :restart, 'service[nginx]'
# end
# disable default site
link '/etc/nginx/sites-enabled/default' do
action :delete
only_if 'test -L /etc/nginx/sites-enabled/default'
notifies :restart, 'service[nginx]'
end
# add my_flask_app site
cookbook_file '/etc/nginx/sites-available/my_flask_app' do
source 'nginx/my_flask_app'
mode 0640
owner 'root'
group 'root'
end
# enable my_flask_app site
link '/etc/nginx/sites-enabled/my_flask_app' do
to '/etc/nginx/sites-available/my_flask_app'
notifies :restart, 'service[nginx]'
end
Set the my_app_nginx
recipe to run
Add this line to recipes/default.rb
:
Here is the complete file:
#
# Cookbook Name:: my_flask_server
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
include_recipe 'apt::default'
include_recipe 'my_flask_server::my_app_dir'
include_recipe 'my_flask_server::my_app_python'
include_recipe 'my_flask_server::my_app_ssh'
include_recipe 'my_flask_server::my_app_deploy'
include_recipe 'my_flask_server::my_app_uwsgi'
include_recipe 'my_flask_server::my_app_nginx'
Run all tests
Open a browser and navigate to http://default-ubuntu-1404/
. You should see the "Hello World!" page of the my_flask application.