ikasama over technology

忘れたくないことを忘れないために

Windows から Docker 実行時に鍵ファイルを渡して SSH したい

問題

$ winpty docker-compose exec proxy ls -l //key
total 4
-rwxr-xr-x    1 root     root          1692 Mar 17 20:42 id_rsa
$ winpty docker-compose exec proxy chmod 0600 //key/id_rsa
$ winpty docker-compose exec proxy ls -l //key
total 4
-rwxr-xr-x    1 root     root          1692 Mar 17 20:42 id_rsa

$ winpty docker-compose exec proxy ssh -i //key/id_rsa $STEP_SERVER_USER@$STEP_SERVER
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0755 for '/key/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/key/id_rsa": bad permissions
Permission denied (publickey).

解決方法

  • docker-entrypoint.sh でマウントされた鍵ファイルのコピーを内部で作って chmod する

github.com

#!/bin/sh
KEY_PATH=/root/.ssh
KEY_FILE=key.pem
KEY_FILEPATH=$KEY_PATH/$KEY_FILE
mkdir -p $KEY_PATH
cat /key/$INPUT_KEY_FILENAME > $KEY_FILEPATH
chmod 0600 $KEY_FILEPATH
SSH="ssh -o 'StrictHostKeyChecking no' \
         -o 'ServerAliveInterval 15' \
         -i $KEY_FILEPATH \
         -D 0.0.0.0:$LOCAL_PORT \
         -N \
         $STEP_SERVER_USER@$STEP_SERVER"
echo $SSH
eval $SSH

踏み台サーバ経由でいろいろ (Web, Git, MySQL) アクセスしたい

社外から社内システムに接続しようと奮闘した記録です。 以下の前提でやっていきます。

  • エンドポイントは変えない
    • アプリケーションに手を入れなくて良い
    • ブラウザのブックマークなどがそのまま使える
  • Windows + Git Bash + Docker

Web アクセスしたい

使うものは以下。

  • SSH トンネリング (ダイナミックポートフォワード)
  • プロキシの自動構成

1. トンネルを掘る

ssh stepserver -f -N -D 1080
  • -f: バックグラウンドで実行
  • -N: コマンドを実行しない
  • -D 1080: ダイナミックポートフォワード。localhost:1080 に SOCKS プロキシをたてる。

これでlocalhost:1080 にプロキシとしてアクセスすると、stepserver 経由で通信ができます。

2. プロキシの設定

まずはプロキシの自動構成スクリプト (proxy.pac) を準備します。

function FindProxyForURL(url, host) {
  if (shExpMatch(host, "stg.*.example.com") || dnsDomainIs(host, "git.example.com")) {
    return "SOCKS5 localhost:1080; DIRECT";
  } else {
    return "DIRECT";
  }
}
  • 上の例はステージング環境のアプリケーションと GitLab に Web アクセスしたい場合のサンプルです。
  • stg.*.example.comgit.example.com にマッチする URL は SOCKS プロキシでアクセスし、他は直接アクセスします。
  • 細かい構文なんかは、以下を参考にすると良いです。

docs.microsoft.com

ブラウザに設定

今回は proxy.pac をローカルに置いてますが、踏み台に Web サーバを立てて、そこに置いてもいいかもしれません。

f:id:ikasamak503:20180311195200p:plain

ChromeIE とかは共通ですけど、Firefox は独自の設定です。お使いのブラウザに合わせて設定してください。

Git アクセスしたい

1. SSH でアクセスする場合

SSH フォワーディングします。

~/.ssh/config

Host stepserver
  HostName ec2-XXX-XXX-XXX-XXX.ap-northeast-1.compute.amazonaws.com
  User ec2-user
  Identityfile ~/.ssh/stepserver.pem

Host git.example.com
    HostName git.example.com
    IdentityFile ~/.ssh/git_id_rsa
    ProxyCommand ssh -W %h:%p stepserver

さっきの SOCKS プロキシを使うこともできます。その場合は ProxyCommand を書き換えます。

 ProxyCommand connect -S localhost:1080 %h %p

-S オプションで SOCKS です。

2. HTTP/HTTPS でアクセスする場合

Git にプロキシの設定をします。 SOCKS プロキシ経由でいけるはずなんですが、今回の環境だとうまく動きませんでした。 Git サーバ側の設定が関係しているかも?

$ git config --global http.proxy localhost:1080
$ git config --global https.proxy localhost:1080
$ git config --list | grep proxy
http.proxy=localhost:1080
https.proxy=localhost:1080
$ git clone http://git.example.com/ikasamak/test.git
Cloning into 'test'...
fatal: unable to access 'http://git.example.com/ikasamak/test.git/': Empty reply from server

MySQL アクセスしたい

ちょっと特殊な環境だったので、ちゃんと説明するとこんな感じです。

Windows 上 の Docker コンテナ (複数) のアプリケーションから MySQL 接続したい。

この場合、複数のコンテナからポートフォワーディングするのはしんどいし、 まずアプリケーションに手を入れたくないので、次の構成にしました。

  • Windows ホストからローカルポートフォワードする *1
  • 各コンテナ内のアプリケーションはホスト上のフォワーディングしたポートに MySQL 接続しに行く

やっていきます。

1. コンテナからアクセスするためのホスト側 IP アドレスを確認する

DockerNAT となっているものはコンテナから出てくるインタフェースなので、そこにはコンテナから繋がりません。 それ以外なら何でもいいようです。 今回は 192.168.11.2 とします。

2. トンネルを掘る

ssh stepserver -f -N -L 192.168.11.2:3306:mysql.example.com:3306
  • -L [bind_address:]port:host:hostport: ローカルポートフォワーディング
    • bind_address: ローカルで待ち受けるアドレス。0.0.0.0 にするとどこからでも受け付けるようになる。
    • port: ローカルで待ち受けるポート
    • host: 接続先のホスト。今回の場合は MySQL サーバのエンドポイント。
    • hostport: 接続先のポート。今回の場合は MySQL のデフォルトなので 3306

3. ホストの hosts で無理やり名前解決する

192.168.11.2  mysql.example.com

試しにコンテナから ping を打ってみると、ちゃんと hosts に書いた IP に飛んでいることが分かります。

$ winpty docker-compose exec db ping -c 1 mysql.example.com
PING mysql.example.com (192.168.11.2): 56 data bytes
64 bytes from 192.168.11.2: icmp_seq=0 ttl=37 time=10.231 ms
--- mysql.example.com ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 10.231/10.231/10.231/0.000 ms

このやり方は、Docker の DNS 設定が Automatic じゃないと出来ないかもしれません。

f:id:ikasamak503:20180312015837p:plain

4. コンテナから接続確認

$ winpty docker-compose exec db bash
root@0258fd400317:/# mysql -h mysql.example.com -u user -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 376064
Server version: 5.5.34-log MySQL Community Server (GPL) by Remi

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

これでうまくいってれば、コンテナ上のアプリケーションもうまく MySQL 接続できると思います!

まとめ

やっていることはどれもだいたい一緒です。

  1. 踏み台に SSHフォワーディング
  2. トラフィックをなんとかフォワーディング先に乗せる

ダイナミックポートフォワーディングは便利なんですが、アプリケーション側が SOCKS プロキシに対応していないと使えないのが難点です。 そこを吸収してくれるソフトウェアもあるっぽいんですが、今回はそこまで踏み込まずにやりました。

あと、Docker for Windows の情報が無さすぎて泣きそうになりました。

参考

*1:ここで SOCKS を使わないのは、アプリケーション側での対応が必要になるからです

この間作った Bot は一定時間経つと死ぬことがわかった

先週 RubyBot をつくりました。

ikasamak503.hatenablog.com

さっそく職場で動かしてみたんですが、半日くらい放置してると死ぬことがわかりました。

そもそも

RubyBot を作るなら、こちらの slack-ruby-bot を使うほうが良さそうです。

github.com

slack-ruby-client の README にも、Bot 作るなら slack-ruby-bot 使えよって書いてあります。 後述しますが、今回僕が作った Bot が死んでる原因にもちゃんと対策されているので、slack-ruby-bot を使うのがいいんじゃないでしょうか。

やむを得ない事情

職場の Slack に Bot をチャンネルに invite する権限がなくて、今は slack-ruby-bot の採用を断念してます。*1 そのうち権限を要求したい。

というわけで自前の Bot を無理やり直しました

github.com

原因

どうやら、RTM API のコネクション (WebSocket) は一定時間でサーバ側から切断されるようです。そりゃそうか。

対策

RTM API のコネクションが切れたら新しい接続を開始するようにしました。

def start!
  # RTM Connection
  client = Slack::RealTime::Client.new

  client.on :hello do
    ...
  client.on :message do |data|
    ...
  client.on :closed do |_data|
    puts "Client has disconnected successfully!"
    start!
  end
end

start!

でもこういうやり方でいいのかしら。ちょっと不安。

*1:slack-ruby-botBot をチャンネルに invite しないと使えません

Bot の発言に反応する Slack Bot を Ruby で作ってました。

2 月から転職して新しい職場でバタバタしたり、風邪でダウンしたりでなかなかブログが書けませんでした。 いろいろ落ち着いてきたので、これからは週1くらいで何か書けたらなと思っています。

ことのはじまり

  • Bot の発言に反応してくれる Bot を作りたい
  • デフォルトで slackbot というものがある

https://your-workspace.slack.com/customize/slackbot f:id:ikasamak503:20180226022651p:plain

  • でも slackbot は Bot の発言には反応してくれない
    • Bot 同士で無限に反応してしまうのを防ぐため?
  • Bot に反応する Bot がないなら作ろう! ← イマココ

つくりました

github.com

動作に必要な環境

  • docker
  • docker-compose
  • WebSocket が通信できる環境

準備

1. Bot を Workspace に追加する

ikasama.slack.com

f:id:ikasamak503:20180226023624p:plain

追加すると API Token が見れるので、控えておく。

f:id:ikasamak503:20180226023853p:plain

反応するチャンネルに Bot を招待する

f:id:ikasamak503:20180226024722p:plain

2. コードを clone して .env をつくる

$ git clone https://github.com/ikasam/slack-bot.git
$ cd slack-bot
$ cp .env.sample .env

.env に設定する項目

SLACK_BOT_TOKEN=xoxp-XXXXXXXXXXX <- さっき控えた API Token
SLACK_CHANNEL=#general           <- 反応するチャンネル
SLACK_CHANNEL_OPS=#bot-test      <- (省略可) 接続成功をお知らせするチャンネル
KEYWORD=hoge                     <- 反応するキーワード

これで準備は完了。

実行

$ docker-compose up -d

これだけ。

動作確認

SLACK_CHANNEL_OPS を設定しているときは、接続成功時に以下のようにメッセージが飛んできます。

f:id:ikasamak503:20180226025754p:plain

Bot の発言にも反応してくれている!

f:id:ikasamak503:20180226030131p:plain

解説

slack-ruby-client とう gem をつかって作りました。

github.com

  • Real Time Messaging API というものを使います。WebSocket で通信するので、プロキシ環境下だと動かないかも。
# RTM Connection
client = Slack::RealTime::Client.new
  • 接続がうまくいったら、Slack が hello を送信してきます。
client.on :hello do
  # 接続時の処理
end
  • あらゆるイベントは message で送られてきます。
client.on :message do |data|
  if (not KEYWORD.empty?) and data.channel == CHANNEL_ID
    case data.text
    when /#{KEYWORD}/ then
      client.message channel: data.channel, text: "<!channel> yo!"
    when /#{BOT_USER_ID}/ then
      client.message channel: data.channel, text: "<@#{data.user}> Hi."
    end
  end
end

もっと細かいことをしたいなら、Slack API のドキュメントを読みながらやると良いと思います。

参考

Docker 上の PHP 環境に Composer コンテナでパッケージをインストールする

https://hub.docker.com/r/composer/composer/

リンク先に書いてあることが全てなんですが、それだとあんまりなのでちょっと説明しておきます。

ざっくりいうと、PHP 環境のコンテナとは別に Composer の入ったコンテナを用意して、 docker run --rm でスポット的に動かします。

1. イメージを pull する

$ docker pull composer/composer

これを書いている時点で、Hub に push されているイメージのバージョンは 1.2.0 でした。

$ docker run --rm composer/composer --version
Composer version 1.2.0 2016-07-19 01:28:52

最新のものがほしければ、ソースからビルドすればよいです。

github.com

2. composer.json を書く

適当に欲しいものを書いてください。

{
  "require": {
    "smarty/smarty": "~3.1",
    "ezyang/htmlpurifier": "*"
  }
}

3. 実行する

$ docker run --rm -v $(pwd):/app composer/composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing smarty/smarty (v3.1.31)
    Downloading: 100%

  - Installing ezyang/htmlpurifier (v4.9.3)
    Downloading: 100%

Writing lock file
Generating autoload files

4. docker run がめんどくさい人は

PATH が通ったところにシェルスクリプトを置きましょう。

$ sudo sh -c "cat >/usr/local/bin/composer" <<'EOF'
#!/bin/sh
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
echo "Current working directory: '"$(pwd)"'"
docker run --rm -v $(pwd):/app -v ~/.ssh:/root/.ssh composer/composer $@
EOF
$ sudo chmod +x /usr/local/bin/composer
$ composer --version
Current working directory: '/home/ikasamak/work/docker-lamp/html/selfphp'
Composer version 1.2.0 2016-07-19 01:28:52

docker-compose で PHP の実行環境を作る

PHP の勉強をはじめるにあたって、最初に取り組んだのが実行環境の構築です。 この記事ではそれを振り返ってみます。

そんなことをしなくても

今時は PHP を書いて実行できる Web サービスはいくらでもあるので、 そういう面倒なのはいいから PHP を書いて動かすことだけしたいという人はそっちを当たると良いと思います。 知っている範囲だと Cloud9paiza.io なんてのがあります。

ぼくは今後役立つだろうと思ったので、あえて手元に実行環境を作る方を選びました。

前提知識

  • Linux はそこそこ触ったことがある
  • Docker についてはなんとなく知っているけれど、使ったことはない

つくる環境

  • いわゆる LAMP 環境です
  • CentOS 7 上に Docker をインストールして使います
  • Docker コンテナ上で PHP, Apache, MySQL を動かします
  • コンテナの立ち上げには docker-compose を使います

環境構築手順

1. Docker インストール

docs.docker.com

2017年12月27日現在、サポートされている CentOS のバージョンは 7 です。 6 以下だったら上げときましょう。

CentOS の Docker CE x86_64 をインストールします。 f:id:ikasamak503:20171227202139p:plain

Docker のインストールに必要なパッケージ。なかったら先にインストールしましょう。

$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

Docker CE のリポジトリを追加します。

$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

Docker をインストール。

$ sudo yum install docker-ce

docker -v でバージョンが表示されたらインストール成功です。

$ docker -v
Docker version 17.03.1-ce, build c6d412e

ちなみに、docker を動かすためには root 権限が必要です。

$ docker images
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.27/images/json: dial unix /var/run/docker.sock: connect: permission denied

それが面倒なら、docker コマンドを実行するユーザを docker グループに所属させましょう。

$ sudo gpasswd -a <username> docker

2. docker-compose インストール

docs.docker.com

こちらはさくっと終わります。

sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

3. Dockerfiledocker-compose.yml の準備

ぼくが使っているものは以下にあります。これをクローンしても動きます。たぶん。

https://github.com/ikasam/docker-lamp

  • docker-compose.yml
version: '2'
services:
  mysql:
    build: ./mysql
    environment:
      MYSQL_ROOT_PASSWORD: pass
    volumes:
      - db:/var/lib/mysql
  php:
    build: ./php
    ports:
      - '8080:80'
    volumes:
      - ./html:/var/www/html
    depends_on:
      - mysql
volumes:
  db:
  • mysql/Dockerfile
FROM mysql:5.7
COPY ./charset-utf8.cnf /etc/mysql/conf.d/
  • php/Dockerfile
FROM php:7-apache
COPY php.ini /usr/local/etc/php/
RUN apt-get update \
  && docker-php-ext-install pdo_mysql mysqli mbstring

4. コンテナの起動

docker-compose.yml があるディレクトリ上で、以下のコマンドを実行します。

$ docker-compose up -d

5. 動作確認

phpMyAdmin を置いて、PHP の動作と MySQL との連携を確かめます。

$ cd html/
$ wget https://files.phpmyadmin.net/phpMyAdmin/4.4.13.1/phpMyAdmin-4.4.13.1-all-languages.tar.gz
$ tar zxf phpMyAdmin-4.4.13.1-all-languages.tar.gz
$ mv phpMyAdmin-4.4.13.1-all-languages myadmin
$ cd myadmin/
$ cp config.sample.inc.php config.inc.php

config.inc.php に書いてある接続先を、localhost から mysql に変更します。

$cfg['Servers'][$i]['host'] = 'mysql';

ブラウザでアクセスします。

今回の例では port を 8080 にしているので、http://<IPアドレス>:8080/myadmin でアクセスします。

f:id:ikasamak503:20171228022609p:plain

id:root、パスワード:pass でログインできれば成功です。

f:id:ikasamak503:20171228022443p:plain

適当な SQL でも投げてみましょう。

参考文献

環境作成にあたって、以下の記事を参考しました。 各設定の詳しい解説はそちらを参照してください。 また、docker-compose 等のコードも fork して使用させていただいています。

qiita.com

qiita.com

PHP の勉強をはじめました

タイトルのとおりです。まずは基礎を独学でがんばります。

書籍を読んで、練習問題とかに取り組んだり取り組まなかったりしています。

使っている教材はこれ。独習できそうな感じですね。

独習PHP 第3版

独習PHP 第3版

書いたコードはこちら。

GitHub - ikasam/selfphp

きっかけ

次の転職先で PHP を使うからです。*1

開発環境

CentOS 上の Docker でやってます。もっと簡単なのもあったと思うけど、Docker の勉強がてらに。

ほとんどは先人の書いた記事を参考にしたんですけど、この辺について別の記事で書きたいと思ってます。

今後の目標

書籍を一通り消化したら、次はフレームワークの勉強をしたいですね。

CakePHP を使うみたいなので、とりあえずはその勉強かな?

いい教材があったら教えて欲しい。

*1:入社までにマスターしますと豪語してしまった……。