小林ノエルのエンジニア的忘備録

フリーランス兼会社員エンジニアが技術とかリモートワークのこととかをツラツラ書いていきます

chefでprovisioningする

今は下火なのかもだけど、chef周りのメモ。案件によってはまだまだ使うので。

インストール

chefをインストールする方法は昔から乱立しててカオスだった記憶があるけど、最近はchefdkで一本化の方向?みたいな雰囲気を感じた。

インストールはhomebrewでやっておくのがよさそう

➜  ~ brew cask install chefdk
➜  ~ chef -v
Chef Development Kit Version: 2.5.3
chef-client version: 13.8.5
delivery version: master (73ebb72a6c42b3d2ff5370c476be800fee7e5427)
berks version: 6.3.1
kitchen version: 1.20.0
inspec version: 1.51.21

リポジトリを作る

リポジトリの作り方も何通りかあるみたいで統一されてないみたい。たとえばgeneratorが面倒みてくれるものでも下記のようなものがある。

➜ ~ chef generate --help
Usage: chef generate GENERATOR [options]

Available generators:
  app             Generate an application repo
  cookbook        Generate a single cookbook
  recipe          Generate a new recipe
  attribute       Generate an attributes file
  template        Generate a file template
  file            Generate a cookbook file
  helpers         Generate a cookbook helper file in libraries
  lwrp            Generate a custom resource
  resource        Generate a custom resource
  repo            Generate a Chef code repository
  policyfile      Generate a Policyfile for use with the install/push commands
  generator       Copy ChefDK's generator cookbook so you can customize it
  build-cookbook  Generate a build cookbook for use with Delivery
    -h, --help                       Show this message
    -v, --version                    Show chef version

どれ使えばええねん。。。apprepoがそれっぽい。repoは昔からよく見るdirectory構成になってるけど、謎のexampleが入ってる。appは最近の構成?らしい。よくわからん。

とりあえずやってみる

以降の構成はベスト・プラクティスなのかはわからないが、とりあえず動くので細かいことは考えないようにしておく。

➜ mkdir my_app_server_repo
➜ cd my_app_server_repo
➜ vi knife.rb

knife.rbの内容は以下の通り

local_mode true
chef_repo_path   File.expand_path('../' , __FILE__)

knife[:ssh_attribute] = "knife_zero.host"
knife[:use_sudo] = true

## SSHエージェントを使ってないなら、SSHログイン用の鍵へのファイルパスを指定しましょう。
# knife[:identity_file] = "~/.ssh/id_rsa"

## Nodeの各種属性(attributes)はローカルにJSONファイルとして保存されていきます。
## automatic_attribute_whitelist オプションは、Nodeから収集したAttributeのうち、保存する対象を絞ることができます。
## NodeもGitで管理する場合は、現状に依存するディスク使用量などは無視してよいでしょう。
knife[:automatic_attribute_whitelist] = %w[
  fqdn
  os
  os_version
  hostname
  ipaddress
  roles
  recipes
  ipaddress
  platform
  platform_version
  cloud
  cloud_v2
  chef_packages
]
cookbook_path [
  File.expand_path('../berks-cookbooks', __FILE__),
  "cookbooks",
]
knife[:before_bootstrap] = "chef exec berks vendor"
knife[:before_converge]  = "chef exec berks vendor"

cookbooksディレクトリを作って、Berksfileリポジトリの直下に持ってくる。

➜  mkdir cookbooks
➜  chef generate cookbook cookbooks/app_server
Generating cookbook app_server
- Ensuring correct cookbook file content
- Ensuring delivery configuration
- Ensuring correct delivery build cookbook content

Your cookbook is ready. Type `cd cookbooks/app_server` to enter it.

There are several commands you can run to get started locally developing and testing your cookbook.
Type `delivery local --help` to see a full list.

Why not start by writing a test? Tests for the default recipe are stored at:

test/integration/default/default_test.rb

If you'd prefer to dive right in, the default recipe can be found at:

recipes/default.rb

➜  mv cookbooks/app_server/Berksfile ./

Berksfileの中身は例として以下のようにしておく。

# frozen_string_literal: true
source 'https://supermarket.chef.io'

cookbook 'nginx', '~> 8.1.2'
cookbook 'ruby_rbenv', '~> 2.0.7'

recipeをかく

cookbooks/app_server/recipes/default.rbを以下のように1行加える

#
# Cookbook:: app_server
# Recipe:: default
#
# Copyright:: 2018, The Authors, All Rights Reserved.

# install nginx from source
include_recipe "nginx::default"  ## ←ここを追加

cookbooks/app_server/metadata.rbを以下のように1行加える

name 'app_server'
maintainer 'The Authors'
maintainer_email 'you@example.com'
license 'All Rights Reserved'
description 'Installs/Configures app_server'
long_description 'Installs/Configures app_server'
version '0.1.0'
chef_version '>= 12.14' if respond_to?(:chef_version)

depends 'nginx'  ## ←ここを追加

recipeを実行

➜  knife zero bootstrap app-server-config --node-name my-app-server1
WARN: Starting local-mode server in deprecated socket mode (CHEF-18) at /opt/chefdk/embedded/lib/ruby/gems/2.4.0/gems/chef-13.8.5/bin/knife:25:in `<top (required)>'.
Please see https://docs.chef.io/deprecations_local_listen.html for further details and information on how to correct this problem.
Doing old-style registration with the validation key at ...
Delete your validation key in order to use your user credentials instead

Connecting to montblanc-intg
app-server-config -----> Installing Chef Omnibus (-v 13)
...
app-server-config Chef Client finished, 0/0 resources updated in 02 seconds

app-server-ssh-config~/.ssh/configに記載されているHost名を入れる想定

➜  knife search node "name:*"
1 items found

Node Name:  my-app-server1
Environment: _default
FQDN:        ip-172-23-7-74.ap-northeast-1.compute.internal
IP:          172.23.7.74
Run List:
Roles:
Recipes:
Platform:    amazon 2017.09
Tags:

➜  knife node run_list add my-app-server1 app_server
my-app-server1:
  run_list: recipe[app_server]
➜  knife zero converge "name:my-app-server1"
Execute command hook in before_converge.
Resolving cookbook dependencies...
Using build-essential (8.1.1)
Using mingw (2.0.2)
Using ruby_rbenv (2.0.7)
...
app-server-config Running handlers:
app-server-config Running handlers complete
app-server-config Chef Client finished, 28/43 resources updated in 23 seconds

ブラウザでserverインスタンスのipを打ち込んでnginxの404 Not foundが表示されればとりあえずの成功。

改良する

上記のログを見るとわかるが、package版がインストールされている。最近はだいぶマシになっているようだが、package版だと最新のstable版のnginxが入らない。最新のstable版を入れる場合は相変わらずソースからコンパイルする必要がある。なのでそのように調整する。

ついでにrubyもいれて、environmentを使ってstagingやproductionにも対応できるようにしておく。

mkdir attributes
➜  mkdir templates
➜  mkdir templates/default
➜  knife environment create integration --disable-editing  ## これはmy_app_server_repo直下でやる

attributes/default.rbに以下の内容を記載する。checksumの調べた方どうやるんだったかな。思い出したら後で追記する。

default['nginx']['install_method'] = 'source'
default['nginx']['source']['version'] = '1.12.2'
default['nginx']['source']['url']      = "http://nginx.org/download/nginx-#{node['nginx']['source']['version']}.tar.gz"
default['nginx']['source']['checksum'] = '305f379da1d5fb5aefa79e45c829852ca6983c7cd2a79328f8e084a324cf0416'
default['nginx']['default_site_enabled'] = false  ## ←後で自分が作ったsite_fileをenableにするので

default['rbenv']['version'] = '2.5.0'

このままconvergeするとエラーになることがあるようなので、その場合はサーバごと新しいインスタンスを作り直したほうが良さそう。

templates/default/my-appとかのnginxのsite fileを作る

upstream unicorn_server {
  server unix:/tmp/unicorn.socket  ## 本当はpumaとかでやりたい
  fail_timeout=0;
}

server {
  listen 80;
  client_max_body_size 4G;
  server_name my-app;

  keepalive_timeout 120;

  # Location of our static files
  root /srv/www/my-app/<%= node.chef_environment %>/current/public;

  location ~ ^/assets/ {
      root /srv/www/my-app/<%= node.chef_environment %>/current/shared/public;
  }

  location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;

      if (!-f $request_filename) {
          proxy_pass http://unicorn_server;
          break;
      }
  }

  error_page 500 502 503 504 /500.html;
  location = /500.html {
      root /srv/www/my-app/<%= node.chef_environment %>/current/public;
  }
}

recipes/default.rbは最終的には以下のようにした。とりあえずここまでやっておけば十分かと。

#
# Cookbook:: app_server
# Recipe:: default
#
# Copyright:: 2018, The Authors, All Rights Reserved.

# 作成したのはintegrationだけだけど、こんな感じで指定可能なenvironmentのチェックを入れておくと良いかも
if !['integration', 'staging', 'production'].include?(node.chef_environment)
  fail "!!SET chef_environment!"
end

include_recipe "nginx::default"

# locate site conf
template "#{node['nginx']['dir']}/sites-available/my-app" do
  source "my-app.erb"
  owner  'root'
  group  node['root_group']
  mode   '0644'
  notifies :reload, 'service[nginx]'
end
nginx_site "my-app" do
  enable true
end

# install packages
package 'Install packages' do
  package_name ['mysql-devel']
end

## install rbenv and ruby
rbenv_system_install 'system_install_foo'
rbenv_plugin 'ruby-build' do
  git_url 'https://github.com/rbenv/ruby-build'
end
rbenv_ruby node['rbenv']['version']
rbenv_global node['rbenv']['version']

## install bundler
rbenv_gem 'bundler' do
  rbenv_version node['rbenv']['version']
end
rbenv_rehash 'rehash'

## create deploy user
user 'deploy' do
  manage_home true
end

## create deploy directory
directory '/srv' do
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end

%W[
    /srv/www
    /srv/www/my-app
    /srv/www/my-app/#{node.chef_environment}
    /srv/www/my-app/#{node.chef_environment}/shared
].each do |path|
  directory path do
    owner 'deploy'
    group 'deploy'
    mode '0755'
    action :create
  end
end

metadata.rbを以下のようにしておくのをお忘れなく。

depends 'nginx'
depends 'ruby_rbenv'  ## ←ここを追加

再度convergeしようとするとエラーになることがあるようなので、その場合はサーバごと新しいインスタンスを作り直したほうが良さそう。

chefつらい。。。やはり今の時代、サーバを直でprovisioningするのはansibleのほうがナウでヤングでホットな気がする。