くんすとの備忘録

プログラミングや環境設定の覚え書き。

sbtの~/.ivy2の場所を変更する

sbtの起動オプションで指定できるらしい。

たとえばsbtで使用する ~/.sbt をカレントの .ivy2 に変更したい場合、以下のように環境変数をセットして実行する。

export SBT_OPTS="-Dsbt.ivy.home=.ivy2"
sbt

マニュアルを見てみると、 ~/.sbt/boot とかも変更できるみたい。

まれに変更したくなるタイミングがあるので忘れる前にメモ。

参考URL

AnsibleのDocker Connection Pluginを使って空ファイルをコピーするとジョブがたまにハングする問題

ためした環境

  • 実行元
    • macOS High Sierra 10.13.6
    • Ansible 2.4.3.0
    • Python 2.7.10
  • 対象サーバー
    • Docker for Mac上のコンテナ
      • Centos 6.10
      • AmazonLinux

再現手順

1. 空ファイルを作成

$ touch empty

2. site.yml を以下の内容で作成

- hosts: all
  connection: docker
  tasks:
    - name: copy empty file
      copy:
        src: empty
        dest: /empty

    - name: remove empty file
      file:
        state: absent
        path: /empty

3. 検証スクリプトを以下の内容で作成

test.sh

#!/bin/bash
set -eu

NAME=$(docker ps -l --format '{{.Names}}')

cat <<++EOS>hosts
[all]
$NAME
++EOS

for ((i=0; i<100; i++)); do
    ansible-playbook -i hosts site.yml
done

4 対象サーバーのdockerコンテナを起動

$ docker run -it --rm centos:6.10 bash

5. 4.とは別のターミナルで検証スクリプトを起動

$ ./test.sh

_人人人人人人人人人_
> そのうち止まる <
 ̄YYYYYYYY

止まったときの状態

コンテナのプロセス

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  11500  2652 pts/0    Ss   15:31   0:00 bash
root      2268  0.0  0.0   8396   556 ?        Ss   15:33   0:00 dd of=/root/.ansible/tmp/ansible-tmp-1536593616.23-267547494832017/source bs=65536
root      2279  0.0  0.0  13380  1872 pts/0    R+   15:34   0:00 ps aux

こんな感じで、dd コマンドで停止しているように見えます。

ansible-playbookの詳細ログ

-vvvオプションを付けて再実行してみます。

$ ansible-playbook -vvv -i hosts site.yml

ログを全部貼ると長いので、停止した箇所付近だけ貼り付けます。

TASK [copy empty file] **************************************************************************************************************************************************
task path: /Users/kunst/work2/ansible-test/site.yml:4
<goofy_goldstine> ESTABLISH DOCKER CONNECTION FOR USER: root
<goofy_goldstine> EXEC ['/usr/local/bin/docker', 'exec', '-i', u'goofy_goldstine', u'/bin/sh', '-c', u"/bin/sh -c 'echo ~ && sleep 0'"]
<goofy_goldstine> EXEC ['/usr/local/bin/docker', 'exec', '-i', u'goofy_goldstine', u'/bin/sh', '-c', u'/bin/sh -c \'( umask 77 && mkdir -p "` echo /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844 `" && echo ansible-tmp-1536593766.1-91378099171844="` echo /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844 `" ) && sleep 0\'']
Using module file /Users/kunst/Library/Python/2.7/lib/python/site-packages/ansible/modules/files/stat.py
<goofy_goldstine> PUT /var/folders/ys/535_fggj5fb3k98v2xz06b0h0000gn/T/tmpEojaf5 TO /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844/stat.py
<goofy_goldstine> EXEC ['/usr/local/bin/docker', 'exec', '-i', u'goofy_goldstine', u'/bin/sh', '-c', u"/bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844/ /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844/stat.py && sleep 0'"]
<goofy_goldstine> EXEC ['/usr/local/bin/docker', 'exec', '-i', u'goofy_goldstine', u'/bin/sh', '-c', u"/bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844/stat.py && sleep 0'"]
<goofy_goldstine> PUT /Users/kunst/work2/ansible-test/empty TO /root/.ansible/tmp/ansible-tmp-1536593766.1-91378099171844/source

原因調査

最初は copy モジュールが怪しいなぁと思って site-packages/ansible/modules/files/copy.py あたりをデバッグしていたのですが、どうやらその前に止まっている模様……

もしやと思い、Docker Connection Pluginのソースを見てみると、そのもの dd コマンドを投げている箇所がありました。 github.com

そのままissueを検索してみると、bugでissueが立っているのを発見。 github.com

あー、これですね。ログの出方も同じ。

応急処置

  • 空ファイルをコピーせず、 touch コマンドなどで作成する
  • 空ファイルじゃなければいいので、適当に半角スペースや改行などを入れて1バイトにする

開発環境をセットアップするAnsible Playbookを作成したよ

前回の続き。

VPS上で生活しよう!ということなので、開発用の各種パッケージをインストールしたり設定ファイルをセットアップしたりするPlaybookを作成しました。

今回は量が多く何度もやり直しをしたのと、今後もちょこちょこメンテして再実行をしたいので、冪等性を保つような作りにしています。

github.com

軽い解説

Roleについて

今回は Role の機能を使ってみました。 Roleを使ってセットアップ内容を以下のように分割しました。

  • common - 共通で使うパッケージのインストール
  • dotfiles - dotfilesのインストール
  • vim - Vimプラグインマネージャ(Dein)のインストール
  • linuxbrew - linuxbrewのインストールと、brewを使ったソフトウェアのインストール
  • nodejs - nodebrewのインストール
  • rbenv - rbenvのインストール
  • python - venvをPythonの環境設定

……なんというか、ほぼ docker-desktop でやっている内容の移植です。

処理の共通化

また、セットアップ処理には同じような作業が多いため、処理を共通化しました。 lib ディレクトリ以下にタスクをまとめたモジュールを作成し、include して使うようにしてみました。

これらのモジュールを使うことで、 tasks には処理の順番を、 vars には処理の中身やパッケージのリストを宣言するという作りで Playbook を作成できるようになりした。 ControllerとModelに分割したようなイメージです。

  • apt-install.yml - 変数 packages に宣言されているパッケージをaptでインストールする
  • execute-commands.yml - 変数 commands に宣言されているコマンドを、ログインシェルで実行する
  • get-url.yml - 変数 `remote_urls に宣言されている url からファイルをダウンロードする
  • git-clone.yml - 変数 remote_repos に宣言されているリポジトリを git clone する
  • make-directories.yml - 変数 dirs に宣言されているディレクトリを作成する
  • set-environments.yml - 変数 envs に宣言されている環境変数を ~/.bash_profile に追記する

ちなみにこのやり方はAnsible Galaxyをちょっと真似したような感じです。

例えばわかりやすい例だと、 rbenv の場合、

  • vars - roles/rbenv/vars/main.yml
dir: "{{ cache }}/rbenv"

remote_repos:
  - repo: https://github.com/sstephenson/rbenv.git
    dest: "{{ dir }}"
  - repo: https://github.com/sstephenson/ruby-build.git
    dest: "{{ dir }}/plugins/ruby-build"
packages:
  - libssl-dev
  - libreadline-dev
  - zlib1g-dev
commands:
  - "eval '$(rbenv init -)'"
  - rbenv install -v 2.4.1
  - rbenv rehash
  - rbenv global 2.4.1
envs:
  - "# RBENV"
  - "export RBENV_ROOT={{ dir }}"
  - export PATH=$RBENV_ROOT/bin:$PATH
  - 'eval "$(rbenv init -)"'
  • tasks - roles/rbenv/tasks/
- name: check rbenv
  shell: "bash -lc 'which rbenv'"
  register: exists
  failed_when: false

- block:
  - include: lib/apt-install.yml
  - include: lib/git-clone.yml
  - include: lib/set-environments.yml
  - include: lib/execute-commands.yml
  when: exists.rc != 0

と書けます。

軽いまとめ

こんな感じで環境を用意できたので、しばらくはブラウザの上で生きていきます。

Cloud9をインストールするAnsible Playbookを作成したよ

前回の続き。

なぜVPSをセットアップしているかというと、Cloud9の環境が欲しかったからなのです。 AWSで使うと高額なので、Conohaで動かそうということなのです。

というわけで、Cloud9のセットアップ手順を Playbook にまとめました。 今回のは短いし何度も使うようなものでもないので、冪等性はないです。

github.com

userport は適当に変えて〼

参考URL

Ansible入門しました

今まで食わず嫌いをしてしまっていたAnsibleに、今更ながら1入門しました。

使う前に抱いていた印象と実際に使ってみた感想を並べてみて、最後に書いてみたPlaybookを載せます。 使う前はあまりいいイメージではなかったのですが、使ってみるとなかなか便利では……という使用感でした。

使う前に抱いていた印象

箇条書きで。

  • 小規模利用にはオーバースペックなのでは?
  • リモートに処理を流し込むだけなら、シェルスクリプトを標準入力経由でsshに流せばいいだけでは……
  • Playbookがシェルスクリプトを難しくしただけのラッパーに見えた
    • 「実際のコマンドは~だから、Playbookにはこう書いて~」みたいなことを考えるくらいなら直接シェルスクリプトを書いた方がシンプルでわかりやすいのでは。

実際に使ってみた感想

今回は「新規作成したVPSに初期セットアップをする」という用途でPlaybookを作成し、実行しました。 基本的に、シェルスクリプトで同じことをする場合との比較だと思ってください。

使用感を箇条書きで。

  • 小規模向きかと言うと微妙だけど普通に使える
    • 対象のホスト名を書いたファイルを用意しなくてはいけないのは若干面倒
      • とはいえ1ファイル作るだけ
    • localhost向けに実行することもできる
  • 再実行しやすい
    • 同じ設定を上書きしない作りになっていたり、回避する手段がちゃんと用意されていたりする2
      • 例えば lineinfile という、ファイルに行を追加する命令(?)を再実行した場合、内容が既に追加されていれば追記は行われない
      • "ifだらけ"現象になりにくいため、見通しがよくなる
  • aptgitなどよく使う命令(?)が用意されており、書き方を自然に統一できる
    • shellcommandは極力使わないほうがよさそう
      • ほぼ使わずにできるようになっているし、必要になったら自分の設計が怪しいかもというサインになりそう

実際に書いたPlaybook

VPS(Conoha)に作成したUbuntuへ、毎度やっている初期セットアップ作業をするPlaybookを書きました。

※ユーザー名とsshd_portは適当に変えてます

※セットアップ直後なのでrootで実行します

- hosts: all
  vars:
    - username: user
    - sshd_port: 12345
  vars_prompt:
    - name: password
      prompt: "Input user password"
      encrypt: sha512_crypt
      private: yes
      confirm: yes

  tasks:
    - name: Add user
      user:
        name: "{{ username }}"
        password: "{{ password }}"
        groups: sudo
        shell: /bin/bash

    - name: Add authorized_keys
      authorized_key:
        user: "{{ username }}"
        key: "{{ lookup('file', '.ssh/authorized_keys') }}"

    - name: SSHD settings
      lineinfile:
        dest: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      with_items:
        - regexp: "^PasswordAuthentication"
          line: "PasswordAuthentication no"
        - regexp: "^ChallengeResponseAuthentication"
          line: "ChallengeResponseAuthentication no"
        - regexp: "^Port"
          line: "Port {{ sshd_port }}"

    - name: Restart sshd
      service:
        name: sshd
        state: restarted

    - name: Configure ufw default rules
      ufw:
        direction: "{{ item.direction }}"
        policy: "{{ item.policy }}"
      with_items:
        - direction: "incoming"
          policy: deny
        - direction: "outgoing"
          policy: allow

    - name: Configure ufw rules
      ufw:
        rule: "{{ item.rule }}"
        port: "{{ item.port }}"
        proto: tcp
      with_items:
        - rule: "limit"
          port: "{{ sshd_port }}"

    - name: Enable ufw logging
      ufw:
        logging: on

    - name: Enable ufw
      ufw:
        state: enabled

    - name: Restart ufw
      service:
        name: ufw
        state: restarted

作ってから5回くらい使ってますが便利ですね。

まとめ的な

以前「メンテナンスのことを考えるとシェルスクリプトよりAnsibleの方がいい」という言説を見かけたんですが、確かにその通りだなと感じました。

サーバの初期セットアップをする用途で考えると、 型のあるシェルスクリプト と言えるかもしれません。


食わず嫌いはいけませんね!


  1. Ansibleがリリースされたのは2012年、今年は2018年。

  2. “ちゃんと用意されている"というのは、"自分でやり方を考えたり、工夫したりしなくてもいい"というニュアンスです。

~/.gitconfig を切り替えるCLIツールを作りました

github.com

解決したかったこと

gitconfigの切り替えが面倒だった

昼休みや空き時間にちょっとしたツールを作るようなことがあり、今までは毎回 ~/.gitconfig を書き換えたり、対象のリポジトリだけ .git/config を書き換えたりする運用をしていました。 が、いくつか問題がありました。

  • うっかり忘れて、コミットユーザー誤爆する
  • 書き換えが面倒くさい
  • ~/.gitconrfig の方はバージョン管理しているので、そもそもあまり書き換えたくない

こういった問題があったので、 ~/.gitconfig をシンボリックリンクにして、リンク先を切り替えるツールを作成しました。

設計

ざっくりこのようなつくりにしました。

  • ~/.gitenv/ 以下に環境の名前ごとのサブディレクトリを作成し、その中に .gitconfig を作成
  • ~/.gitconfig へシンボリックリンクを張る
  • リンク先をコマンドラインで切り替える

使い方の例

※既存の ~/.gitconfig は退避しておいてください

# ~/.gitconfig を Alice 用の設定にする
$ gitenv -c alice

# Alice のユーザー設定をする
$ git config --global user.name "Alice"
$ git config --global user.email alice@example.com
$ cat ~/.gitconfig
[user]
    name = Alice
    email = alice@example.com

# ~/.gitconfig を Bob 用の設定に切り替える
$ gitenv -c bob

# Bob のユーザー設定をする
$ git config --global user.name "Bob"
$ git config --global user.email bob@example.com
$ cat ~/.gitconfig
[user]
    name = Bob
    email = bob@example.com

$ ~/.gitconfig を Alice 用の設定に戻す
$ gitenv -c alice
$ cat ~/.gitconfig
[user]
    name = Alice
    email = alice@example.com

# 現在の環境の名前を表示する
$ gitenv
alice (Alice)

実運用

実際に使っている ~/.gitconfig の内容

こんな感じで Include だけを書いてます。

[Include]
    path = ~/.gitconfig.all
    path = ~/.gitaliases
    path = ~/.gitconfig.kunst1080  # この行を環境ごとに変えて〼

プロンプト

普段は zsh を使っているのですが、環境変数 PROMPT$(gitenv) を埋め込むようにしました。 これで今の設定がどれになっているのかわかります。

プロンプト:

hoge@MacBook-Pro.local ~  env2 (kunst1080)
$ 

まとめ

これで誤爆を防げるよ。やったね!

古いfind(1)と新しいfind(1)

ソース読むときのためのメモ。

https://www.gnu.org/software/findutils/manual/html_mono/find.html#fts

The findutils source distribution contains two different implementations of find. The older implementation descends the file system recursively, while the newer one uses fts. Both are normally installed.

If the option --without-fts was passed to configure, the recursive implementation is installed as find and the fts-based implementation is installed as ftsfind. Otherwise, the fts-based implementation is installed as find and the recursive implementation is installed as oldfind.

「findutilsソースディストリビューションには、findの2つの異なる実装が含まれています。古い実装はファイルシステムを再帰的に降下させ、新しいものはftsを使用します。どちらも通常インストールされています」

「configureに--without-ftsオプションが渡された場合、再帰的実装はfindとしてインストールされ、fts-based実装はftsfindとしてインストールされます。それ以外の場合、ftsベースの実装はfindとしてインストールされ、再帰実装はoldfindとしてインストールされます」

v4.6.0のソース

root/find/Makefile.amより

http://git.savannah.gnu.org/cgit/findutils.git/tree/find/Makefile.am?h=v4.6.0

# We always build two versions of find, one with fts (called "find"),
# one without (called "oldfind").  The oldfind binary is no longer
# installed.
bin_PROGRAMS     = find
check_PROGRAMS   = oldfind
find_SOURCES     = ftsfind.c
oldfind_SOURCES  = oldfind.c
man_MANS         = find.1

root/find/ftsfind.c より

http://git.savannah.gnu.org/cgit/findutils.git/tree/find/ftsfind.c?h=v4.6.0

L567

     while ( (errno=0, ent=fts_read (p)) != NULL )

root/find/oldfind.c より

http://git.savannah.gnu.org/cgit/findutils.git/tree/find/oldfind.c?h=v4.6.0

L1425

   dp = readdir (dirp);

コミットログ確認

2005-11-21 05:42:27 +0000

Findutils 4.3.x defaults to using the the FTS implementation of find.

http://git.savannah.gnu.org/cgit/findutils.git/commit/?id=f0759ab8db9cab16699fba45fa6117ef06620194

広告