長生村本郷Engineers'Blog

千葉県長生村本郷育ちのエンジニアが書いているブログ

docker build 時に Text file busy で shell が実行できない対策

f:id:kenzo0107:20180418215217j:plain

概要

Dockerfile 内に以下のように shell の実行を記述していました。

RUN chmod +x hoge.sh \
  && hoge.sh

上記記述のある状態で docker build 実行した所、以下のようなエラーに遭遇しました。

/bin/sh: hoge.sh: Text file busy

What is Text file busy ?

書き込みのために現在開いている手続きのみの (共用テキスト) ファイルを実行しようとした場合や、実行中の手続きのみのファイルを書き込みのために開こうとしたり、削除しようとしたりする場合に発生します。

上記鑑みると chmod +x hoge.sh 実行中に hoge.sh を実行しようとしたが為に発生しているということ?? と推測。

環境情報

  • Ubuntu 14.04.5 LTS \n \l
  • Docker version 17.05.0-ce, build 89658be
  • Base Image: ruby:2.5-alpine

対策

以下 sync 処理を追加し無事問題解決できました。

RUN chmod +x hoge.sh \
  && sync \
  && hoge.sh

What is sync command ?

sync - システム管理コマンドの説明 - Linux コマンド集 一覧表

参考

github.com

qiita.com

curl で FTPS (File Transfer Protocol over SSL/TLS) 接続確認

f:id:kenzo0107:20180418082402p:plain

以下コマンドで FTPS 接続確認ができます。

curl -u <user> --ftp-ssl -k ftp://<ftp domain>/

概要

備忘録記事です。

社外向けに FTPS で接続許可をする必要があり設定しました。

単純に作成・更新した user, password で認証をパスできるか、 の確認だけができれば良いので、その確認方法を模索している時に 程よいコマンドがありました。

その接続確認を FileZilla, Cyberduck でしましたが どうもうまくいかず。。

改めて、 lftp とか色々 ftp だけでもコマンドは多々あるんだなと実感しました。

ECR にログイン(aws ecr get-login)無しでプッシュする

f:id:kenzo0107:20180307231703p:plain

概要

Docker version 1.11 で実装された credential-helper を利用し
ECR へのプッシュを安全に簡易的に行う仕組みを実装します。

Docker ver 1.11 以上にアップグレード

$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
$ sudo sh -c "echo deb https://apt.dockerproject.org/repo ubuntu-trusty main\
> /etc/apt/sources.list.d/docker.list"
$ sudo apt-get purge lxc-docker docker
$ sudo apt-get update
$ sudo apt-get install docker-engine
$ sudo service docker restart

pull Dockerized ECR credential helper

$ docker pull pottava/amazon-ecr-credential-helper

認証設定

以下3つの中から1つ利用ください。 EC2 であれば、1. インスタンスロールで認証 が一番すっきりしていてコードの見通しが良いです。

  1. インスタンスロールで認証
  2. credential で認証
  3. 環境変数で認証

1. インスタンスロールで認証

docker run --rm \
  -e REGISTRY=123457689012.dkr.ecr.us-east-1.amazonaws.com \
  pottava/amazon-ecr-credential-helper
sudo sh -c 'cat << EOF > /usr/bin/docker-credential-ecr-login
#!/bin/sh
SECRET=\$(docker run --rm \\
  -e METHOD=\$1 \\
  -e REGISTRY=\$(cat -) \\
  pottava/amazon-ecr-credential-helper)
echo \$SECRET | grep Secret
EOF'

sudo chmod +x /usr/bin/docker-credential-ecr-login

2. credential で認証

docker run --rm \
  -e REGISTRY=123457689012.dkr.ecr.us-east-1.amazonaws.com \
  -v $HOME/.aws/credentials:/root/.aws/credentials \
  pottava/amazon-ecr-credential-helper
sudo sh -c 'cat << EOF > /usr/bin/docker-credential-ecr-login
#!/bin/sh
SECRET=\$(docker run --rm \\
  -e METHOD=\$1 \\
  -e REGISTRY=\$(cat -) \\
  -v $HOME/.aws/credentials:/root/.aws/credentials \\
  pottava/amazon-ecr-credential-helper)
echo \$SECRET | grep Secret
EOF'

sudo chmod +x /usr/bin/docker-credential-ecr-login

3. 環境変数で認証

export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
docker run --rm \
  -e REGISTRY=123457689012.dkr.ecr.us-east-1.amazonaws.com \
  -e AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY \
  pottava/amazon-ecr-credential-helper
sudo sh -c 'cat << EOF > /usr/bin/docker-credential-ecr-login
#!/bin/sh
SECRET=\$(docker run --rm \\
  -e METHOD=\$1 \\
  -e REGISTRY=\$(cat -) \\
  -e AWS_ACCESS_KEY_ID \\
  -e AWS_SECRET_ACCESS_KEY \\
  pottava/amazon-ecr-credential-helper)
echo \$SECRET | grep Secret
EOF'
sudo chmod +x /usr/bin/docker-credential-ecr-login

credential 保存設定

mv $HOME/.docker/config.json $HOME/.docker/config.json.org

cat << EOF > $HOME/.docker/config.json
{
    "credsStore": "ecr-login"
}
EOF

これで aws ecr get-login から解放されます♪

Linux に rbenv をセットアップして ruby バージョンを切り替える

f:id:kenzo0107:20180307225204p:plain

概要

サーバの ruby のバージョンが古かった為、 rbenv で ruby のバージョンを切り替える様にした際の設定メモです。

setup rbenv

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
 
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
 
$ source ~/.bash_profile
$ rbenv --version
rbenv 1.1.1-30-gc8ba27f

rbenv 経由で Ruby 2.5.0 インストール

$ rbenv install 2.5.0

// 現 version は system. まだ 2.5.0 に切り替わっていない
$ rbenv versions
* system (set by /home/vagrant/.rbenv/version)
  2.5.0

// 2.5.0 へ切り替え
$ rbenv global 2.5.0

// 切り替え確認
$ rbenv versions
  system
* 2.5.0 (set by /home/vagrant/.rbenv/version)

$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]

// リフレッシュしないと .rbenv/versions/2.5.0/bin 以下のパスを通らない
$ rbenv rehash

Datadog Agent v6.0.0-beta.7 にアップデートして Logging 機能を試す!

f:id:kenzo0107:20180110145104p:plain

2017年末にβ版ですが、Datadog の Log 可視化ツールの利用が発表されました。

  • Unifying the views でグラフの高負荷時刻付近のログを参照する機能があったり
  • Elasticsearch+Fluentd の代替として期待できそう

と思い早速導入してみました。

datadog-agent インストール方法

2018年1月10日時点では 5系がインストールされます。

https://docs.datadoghq.com/agent/

5系、6系とで主に変わった点

  • Datadog 設定ファイルパス変更
5系 6系
ベースディレクト /etc/dd-agent /etc/datadog-agent
各種設定ファイル /etc/dd-agent/conf.d/nginx.yaml /etc/dd-agent/conf.d/nginx.d/conf.yaml
メトリクス情報 dd-agent info datadog-agent status

6系では dd-agent コマンドがありませんでした。

  • dd-agent configcheck に該当するコマンドが見当たらない?
    どこにあるのか教えてください(;>_<)

5系からのアップグレード方法

https://github.com/DataDog/datadog-agent/blob/master/docs/beta.md

自身の環境は Ubuntu 16.04.2 LTS だったので以下方法でアップグレードしました。

$ DD_UPGRADE=true bash -c "$(curl -L https://raw.githubusercontent.com/DataDog/datadog-agent/master/cmd/agent/install_script.sh)"

...
Error: /etc/datadog-agent/datadog.yaml seems to contain a valid configuration, run the command again with --force or -f to overwrite it
Automatic import failed, you can still try to manually run: datadog-agent import /etc/dd-agent /etc/datadog-agent

Error と出るので一瞬ハッとしましたが、Error Message をよく見ると
6系の /etc/datadog-agent/datadog.yaml は問題ない設定となっている様に見えますが、上書きしたい場合は --force を使ってね、
とあります。

datadog-agent のアップグレードは無事完了していました。

$ sudo datadog-agent status

Getting the status from the agent.

===================
Agent (v6.0.0-rc.2)
===================
...
...

また各種設定(/etc/datadog-agent/conf.d, checks.d)ファイルも問題なく移行できていました。

5系の設定ファイルを 6系へオーバーライド

特に上記の手法で問題ないですが強制的にオーバーライドする方法を明記しておきます。

// /etc/dd-agent/conf.d 以下のファイルを 6系へ移行
$ /opt/datadog-agent/bin/agent/agent import /etc/dd-agent /etc/datadog-agent --force
 
Success: imported the contents of /etc/dd-agent/datadog.conf into /etc/datadog-agent/datadog.yaml
Copied conf.d/http_check.yaml over the new http_check.d directory
Copied conf.d/network.yaml over the new network.d directory
Copied conf.d/nginx.yaml over the new nginx.d directory
Copied conf.d/process.yaml over the new process.d directory
Copied conf.d/process_check.yaml over the new process_check.d directory
Copied conf.d/ssl_check_expire_days.yaml over the new ssl_check_expire_days.d directory
Copied conf.d/unicorn_check.yaml over the new unicorn_check.d directory
Error: unable to list auto_conf files from /etc/dd-agent: open /etc/dd-agent/conf.d/auto_conf: no such file or directory

// /etc/dd-agent/checks.d/ 以下のファイルを 6系へ移行
$ sudo -u dd-agent -- cp /etc/dd-agent/checks.d/*.py /etc/datadog-agent/checks.d/

nginx log を Logging へ送付

  • /etc/datadog-agent/conf.d/nginx.d/conf.yaml
init_config:

instances:
  - nginx_status_url: http://localhost/nginx_status/

logs:
  - type: file
    service: hogehoge
    path: /var/log/nginx/access.log
    source: nginx
    sourcecategory: nginx_access

  - type: file
    service: hogehoge
    path: /var/log/nginx/error.log
    source: nginx
    sourcecategory: nginx_error

基本的に logs ディレクティブを記述することで OK

  • /etc/datadog-agent/conf.d/fluentd.d/conf.yaml
init_config:

instances:
    -  monitor_agent_url: http://localhost:24220/api/plugins.json
       tag_by: type

logs:
    - type: file
      service: hogehoge
      path: /var/log/td-agent/td-agent.log
      source: td-agent
      sourcecategory: td-agent

datadog.conf 修正

/etc/datadog-agent/datadog.yaml に以下設定を加えます。

log_enabled: true

設定反映

$ sudo systemctl restart datadog-agent

うまく Datadog に反映されないときは

ログを見てみます。

$ sudo tail -f /var/log/datadog/agent.log

...
2018-01-07 11:01:58 JST | INFO | (logs-agent.go:75 in func1) | open /var/log/nginx/access.log: permission denied
...

パーミッションエラーが発生しており
datadog-agent を起動している dd-agent ユーザからアクセスできない状態となっていました。

対処

単純に /var/log/nginx/access.log に 0644 (-rw-r--r--) を付与するだけでなく、
logrotate で生成される新たな log のパーミッションにも注意します。

/var/log/nginx/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 0644 www-data adm
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi \
        endscript
        postrotate
                invoke-rc.d nginx rotate >/dev/null 2>&1
        endscript
}

元々 0640 でしたが 0644 で生成するようにしました。
これにて解決♪

Datadog Logging で確認

ログが流れてくるのを確認できました。
Kibana の Discover ページのような作りです。

f:id:kenzo0107:20180110155520p:plain

今後フィルタリングしてグラフを作ったりできたりしてくるのか、
Pro版なら無料で使わせてもらえないかな、
なんて期待が高まっております

お願い、Datadog さん(-人-)

Datadog で Rails Unicorn の Memory, Idle|Busy Worker 監視 〜呉越同舟〜

f:id:kenzo0107:20171223193555p:plain

概要

Rails の乗っているホストへ Datadog で Unicorn を監視しようとした所、
それらしい Integration がありません*1

ということで独自スクリプトを作成しようと思いました!

独自スクリプトを書こうとしてたら...

同僚「Mackerel なら plugin ありますよ?」

自分「えっ?...」

Mackerel 入ってる

Mackerel に unicorn 監視用の plugin がありました。

mackerel-plugin-unicorn

はてなさんもOSSで出して頂いている、
車輪の再開発は時間の無駄、
人生は一度しかないのでこの Mackerel プラグインを Datadog で使わせて頂こうと思いました。

Mackerel + Datadog 呉越同舟スクリプト

from checks import AgentCheck
import subprocess
import re
class UnicornCheck(AgentCheck):
  def check(self, instance):
    pidfile = instance['pidfile']
    cmd = "/usr/bin/mackerel-plugin-unicorn -pidfile=%s" % (pidfile)

    res = self.exeCmdWithStripLF(cmd)

    for r in res:
        y = re.split(r'\t+', r.rstrip('\t'))
        metrics = y[0]
        out     = y[1]
        self.gauge(metrics, out)

  # コマンド実行結果から改行コードから取り除く
  def exeCmdWithStripLF(self, cmd):
    res = self.exeCmd(cmd)
    return [str(x).rstrip("\n") for x in res]

  # コマンド実行
  def exeCmd(self, cmd):
    return subprocess.Popen(
      cmd,
      stdout=subprocess.PIPE,
      shell=True
    ).stdout.readlines()

Unicorn の PID ファイルを指定します。

init_config:

instances:

  - pidfile: /path/to/rails_project/shared/tmp/pids/unicorn.pid

Datadog Agent 設定ファイルチェック

$ sudo dd-agent configcheck

unicorn_check.yaml is valid

Datadog Agent 再起動

$ sudo service datadog-agent restart

数分後グラフを見てみる

出てきた!

f:id:kenzo0107:20171223203723p:plain

総評

これで呉越同舟型モニタリングができました!

自分自身が呉でも越でもない所に若干の背徳感がありますが
手っ取り早く舟をこしらえたことに本記事の意味があるかと
筆を取りました。

参考になれば幸いです。

伊藤食品 美味しい鯖味噌煮 190g×4缶

伊藤食品 美味しい鯖味噌煮 190g×4缶

*1:あったら教えてください >_<

terraform workspace で環境毎に tfsate 管理

f:id:kenzo0107:20171205214728j:plain

概要

Terraform tfstate の管理をかつて
0.8 系では -backend-config でせっせと環境(stg,prod) 毎に bucket を変えて、
なんてコードを見てきました。

ですが、 workspace で 1つの bucket に 環境毎に保管できる様になりました。

厳密には環境毎でなくとも
リソースの集合毎、module 毎等で管理するイメージですが

今回はイメージを捉えやすく環境毎で分けました。

歴史

  • 0.5 で S3 で管理、
  • < 0.9 では、 remote config で管理場所を設定
  • = 0.9 では、terraform workspace で同一ディレクトリで複数のリソース群を管理

とより利用しやすくなりました。

前提

以下条件とします。

  • tfstate は backend.tf で s3 管理

移行手順

既存 terraform で tfstate 確認

  • 想定の実行計画通りか確認します。
  • 異なる場合は、そもそも現環境と差分が生じている、及び、tfstate が正しく取得できていない等、問題ありなのでそちらを修正します。
$ terraform plan

tfstate ファイル取得

local に terraform.tfstate を取得します。 中身を確認してリソースの設定がある程度問題ないか確認しておきます。

  • 0.8 系
$ terraform remote config \
-backend=s3 \
-backend-config="bucket=tfstate.bucket" \
-backend-config="key=terraform.tfstate" \
-backend-config="region=ap-northeast-1" \
-backend-config="profile=aws-hogehoge"
  • 0.9 系以降
macOS%$ terraform state pull > terraform.tfstate

terraform 0.11.x (2017年12月現在最新) へバージョンアップ

Homebrew ならば upgrade で!

macOS%$ brew upgrade terraform

state 管理を backent.tf で記述

既にこの様に設定されている方はスキップです。特に普遍的な書き方です。

terraform {
  backend "s3" {
      bucket  = "tfstate.bucket"
      key        = "terraform.tfstate"
      region   = "ap-northeast-1"
      encrypt = true
      profile   = "aws-hogehoge"
    }
}

Workspace 作成

  • Workspace stg 作成
$ terraform workspace new stg
  • workspace リスト一覧
$ terraform workspace list
  default
* stg

tfstate を push

$ terraform state push -force .terraform/terraform.tfstate

これで S3 tfstate.bucketenv:/stg/ ディレクトリ以下に terraform.tfstate が push されました。 実際に S3 を見て確認してみてください。

f:id:kenzo0107:20171205213108p:plain

env でなく env: なのが肝です。

実行計画確認

$ terraform plan

想定の実行計画通りか確認して問題なければ移行完了です。

おまけ

terraform を指定したバージョンで実行するには
one-off Container で実行できる様に Makefile でラップする、 が今の所自分の中のベストプラクティスです。

これによって local 環境に依存せず指定したバージョンの terraform 実行が可能となります。

one-off Container とは

one-off Container は Docker コンテナを run --rm で1度のコマンド実行の為だけに起動する手法です。

Makefile で Docker コマンドをラップしておくと
TERRAFORM_VERSION を変更するだけで
指定の terraform バージョンを利用できます。

以下は 0.11.1 の例です。

TERRAFORM_VERSION=0.11.1

DOCKER=docker run --rm -v ~/.ssh:/root/.ssh:ro -v ~/.aws:/root/.aws:ro -v ${PWD}:/work -w /work hashicorp/terraform:${TERRAFORM_VERSION}

$(eval ENV := $(shell ${DOCKER} workspace show))

ifeq (${ENV}, default)
$(error select workspace ! ex: make init ENV=<stg|prod>)
endif

default: init

init:
    # tfstate ファイル初期化
    ${DOCKER} init
    # workspace 作成. "; true" は既に作成済みエラーをスキップする為
    ${DOCKER} workspace new ${ENV}; true
    # 作成した workspace を選択
    ${DOCKER} workspace select ${ENV}
    # 作成した workspace の tfstate ファイルを同期
    ${DOCKER} init

plan:
    ${DOCKER} plan 

apply
    ${DOCKER} apply -auto-approve
  • make init ENV=stg 実行で以下まとめてました
    • tfstate 初期化
    • workspace stg 作成
    • 選択したworkspace の tfstate で初期化

きっとさらに素敵なベストプラクティスがあれば教えてください!

参考になれば幸いです。

DevOpsを支えるHashiCorpツール大全 ThinkIT Books

DevOpsを支えるHashiCorpツール大全 ThinkIT Books