@dobassy 技ログ

自身が触れたITに関する曖昧な情報をわかりやすく表現したいと思っているブログ

Docker コンテナで Redmine 4.0 へのアップグレード作業まとめ

この記事は、sameersbn/redmine コンテナを用いた Redmine 4.0.x へのバージョンアップにおける知見をメモするものです。GOALは Redmine 3.4.7 から Redmine 4.0.x (sameersbn/redmine:4.0.3-1) へのアップグレード完了です(結果的には、できたけど保留、となりました)。

Redmine は利用プロジェクト・人によりプラグイン構成等全く異なるため、これそのままが役に立つものでないと思いますが、類似ケースでの問題点の解決などアップグレードの一助になればと思うところです。

Redmineコンテナ

sameersbn/redmine は、Redmineサービスを提供するDockerイメージで、(おそらく)Officialレポジトリに次いで使われている定番イメージです。

github.com

Repository Downloads Stars
https://hub.docker.com/_/redmine 10M+ 728
https://hub.docker.com/r/bitnami/redmine 500K+ 38
https://hub.docker.com/r/sameersbn/redmine 10M+ 301

(2019/04/21現在)

何が便利かといえば、Available Configuration Parameters にある通り多様なパラメタによる細かい機能設定で、大部分のニーズを満たせるのではないでしょうか。指定パスにプラグインを配置してコンテナを起動するだけで Migration を自動でやってくれる等、おかげで面倒くさいセットアップのかなりの部分を省略してくれます。

redmine 4.0 魅力機能

さて、Redmine 4.0 になぜするのか?というところですが、このあたりの記事で惹かれるかどうか。個人的にはガントチャートの改善や、## 記号によるチケットタイトルの自動展開なんかが気になります。

Pluginの対応状況チェック

[注] git pull して更新があったものを "更新あった" と記載しているため、"Redmine 4.0.x 対応があった" の意味ではない。少々強引だが更新があれば Redmine 4.X 対応しているだろうの期待のもと、Try&Errorで進めるスタンスのため。

更新あった

  • redmine_default_custom_query
  • redmine_issue_templates
  • redmine_issues_summary_graph
  • redmine_pivot_table
  • redmine_wiki_extensions
  • view_customize
  • redmine_work_time

更新なかった

更新がないものは、Redmine 4.x で使えない可能性が高いことを意味します。

$ clipboard_image_paste ❯❯❯ git pull
Already up to date.

clipboard_image_paste はめちゃ便利なので使い続けたい。お気に入りです。

$ redmine-slack ❯❯❯ git pull
Already up to date.

たぶん今なら redmine_messenger plugin を使ったほうがいい。commit log にも redmine 4.x 対応の記載あり。

github.com

$ redmine_github_hook ❯❯❯ git pull
Already up to date.
$ redmine_issue_completion ❯❯❯ git pull
Already up to date.
$ redmine_tweaks ❯❯❯ git pull
Already up to date.

ほかは、今となってはあまり必要ないかな。

git管理していないもの

  • easy_gantt : 結果消すこととする(詳細後述)
  • redmine_agile : 結果消すこととする(詳細後述)

作業

順番注意。Redmine 3.x のままやることがあるのにプラグインだけ先行してアップデートしてしまうと、動かなくなるので注意(Redmine 4.x対応しているものは、4.x以上の縛りを設定しているものも多い)

作業1. Pluginアンインストール

現在利用中のプラグインをアンインストールする方法として、参考にいくつか記載します。

redmine_agile plugin

パット見面白いのだがFree版だと「かんばん風の見た目も楽しめる」程度のもので、実用的かというとちょっと厳しいかなという印象。例えば、Agileタブを開いた時に表示したい状態をカスタムフィルタで保存することができないので、毎回選び直さなければならない点(パラメタを含むURLでがんばって運用できなくもないが)。

Webサービス(Redmine)コンテナを通常起動したあとに docker exec -it コンテナ名 bash して以下のコマンドを実行。

entrypoint.sh app:rake redmine:plugins:migrate NAME=redmine_agile VERSION=0 RAILS_ENV=production
Running raketask redmine:plugins:migrate...                                                                                                   
Migrating redmine_agile (Redmine Agile plugin (Light version))...                                                                             
== 5 AddStoryPointsToAgileRanks: reverting ====================================                                                               
-- remove_column(:agile_data, :story_points, :integer)                                                                                        
   -> 0.0834s                                                                                                                                 
== 5 AddStoryPointsToAgileRanks: reverted (0.1472s) ===========================                                                               

... 省略

== 1 CreateIssueStatusOrders: reverting =======================================
-- remove_index(:issue_status_orders, {:column=>:position})
   -> 0.0191s
-- remove_index(:issue_status_orders, {:column=>:issue_id})
   -> 0.0207s
-- drop_table(:issue_status_orders)
   -> 0.0084s
== 1 CreateIssueStatusOrders: reverted (0.0488s) ==============================

Pluginの削除:

# sameersbn/redmine コンテナ内で
rm -rf /home/redmine/data/plugins/redmine_agile

[注] /home/redmine/redmine/plugins ではありません!詳細は記事下部の参考情報セクションに記載しています。

easy gantt plugin

error:

redmine_1  | NoMethodError: undefined method `to_prepare' for ActionDispatch::Reloader:Class
redmine_1  | /home/redmine/redmine/plugins/easy_gantt/after_init.rb:43:in `<top (required)>'
redmine_1  | /home/redmine/redmine/plugins/easy_gantt/init.rb:24:in `require_relative'
redmine_1  | /home/redmine/redmine/plugins/easy_gantt/init.rb:24:in `<top (required)>'

Redmine Gantt Plugin - Easy Redmine

公式にある通り Redmine 4 対応は完了しているのだが、わざわざPluginによるガントチャート機能を利用していたモチベーションは以下の通り。

  • もともとオリジナルのものはチケットタイトル幅が狭く表示がよく切れる
  • ガントチャート画面でチケットのステータス変更が一切できない
  • EasyGanttではドラッグで開始・期日を設定でき、まとめて設定反映できる(便利)
  • チケット関連付けもドラッグでできる

このなかで半分はRedmine 4にて解消されているので、とりあえず標準のもので過ごしてみようと思う。
ドラッグでの日付設定機能はオリジナルでは全く敵わないところですが、Free版EasyGanttだと複数プロジェクトを跨いだガントチャート表示ができない制約があり、なんとも中途半端な状況だったので消すことの悔いもなし。

モデル削除:

entrypoint.sh app:rake redmine:plugins:migrate NAME=easy_gantt VERSION=0 RAILS_ENV=production
Running raketask redmine:plugins:migrate...
Migrating easy_gantt (Easy Gantt plugin)...
== 20170224134615 UpdateRestApiSettings: reverting ============================
== 20170224134615 UpdateRestApiSettings: reverted (0.0000s) ===================

== 20170213152215 AddDefaultPrintableTemplate: reverting ======================
== 20170213152215 AddDefaultPrintableTemplate: reverted (0.0000s) =============

Pluginの削除:

# sameersbn/redmine コンテナ内で
rm -rf /home/redmine/data/plugins/easy_gantt

[注] /home/redmine/redmine/plugins ではありません!詳細は記事下部の参考情報セクションに記載しています。

redmine_tweaks plugin

redmine_tweaks はもともと怪しかった(設定がうまく反映できないバグや、migrationを使わずにredmine本体のテーブル利用している設計思想などの)ため redmine 4.x 対応は厳しいかなと思っていた。ヘッダメニューなんかは地味に便利に使っていたのだが、レポジトリもread-onlyになってしまっていたので更新が期待できず、今後のredmine追従に支障が生じるため削除。

redmine_tweaks はDBを利用しているがRedmineの settings テーブルに書き込んでいて migration が無い。なのでファイルだけ消すことにする。(plugin_redmine_tweaksというnameでsettingsテーブルに存在している)

なので、Migrationしても何も起きません。

$ entrypoint.sh app:rake redmine:plugins:migrate NAME=redmine_tweaks VERSION=0 RAILS_ENV=production
Running raketask redmine:plugins:migrate...
Migrating redmine_tweaks (Tweaks)...
$

Pluginの削除:

# sameersbn/redmine コンテナ内で
rm -rf /home/redmine/data/plugins/redmine_tweaks

[注] /home/redmine/redmine/plugins ではありません!詳細は記事下部の参考情報セクションに記載しています。

redmine_issue_completion plugin

https://github.com/insspb/redmine_issue_completion

error

redmine_1  | Migrating plugins. Please be patient, this could take a while...
redmine_1  | rake aborted!
redmine_1  | NoMethodError: undefined method `to_prepare' for ActionDispatch::Callbacks:Class
redmine_1  | /home/redmine/redmine/plugins/redmine_issue_completion/init.rb:3:in `<top (required)>'

to_prepare メソッドを使っていて Redmine 4 では利用できなかった。
1ファイルだけのシンプルなPluginなので若干の修正で利用できるようになるものの、独自運用は辛いこと、および機能要件もシンプルなので view_customize plugin にて javascript で同等の操作性を実現するなど代替手段もあり、致命的じゃないし削除する。

作業2. Pluginのアップデート

まずは各pluginディレクトリに移動して git pull し、更新があるかチェックします。
(とりあえず git pull というのも少々乱暴なので、本当は各プロジェクトページで更新情報を確認しましょう)

以下、不都合あった Plugin のメモ。

clipboard_image_paste plugin

redmine_1  | rake aborted!
redmine_1  | NoMethodError: undefined method `to_prepare' for ActionDispatch::Callbacks:Class          
redmine_1  | /home/redmine/redmine/plugins/clipboard_image_paste/init.rb:32:in `<top (required)>'

clipboard_image_paste は pull request が出ているようだが、2019/04/21現在まだ merge されていない。 https://github.com/peclik/clipboard_image_paste/pull/80/files

一旦、pull request元の @Utopism のレポジトリからpullすることにする。

github.com

git remote add branch-v4 https://github.com/Utopism/clipboard_image_paste.git
git pull branch-v4 master

redmine_github_hook plugin

Errorが出て動かない。

$ more log/unicorn.stderr.log

I, [2019-04-21T15:59:49.539450 #302]  INFO -- : Refreshing Gem list
/home/redmine/redmine/plugins/redmine_github_hook/app/controllers/github_hook_controller.rb:4:in `<class:GithubHookController>': undefined method `skip_before_filter' for GithubHookController:Class (NoMethodError)
        from /home/redmine/redmine/plugins/redmine_github_hook/app/controllers/github_hook_controller.rb:3:in `<top (required)>'

commit log も2年ほど内容変更がないみたい。RedmineへのGitレポジトリ連携は直近主要因ではないので、今回はバッサリ切ることにする。
https://github.com/koppen/redmine_github_hook/commits/master

手順超サマリ

後日へのカンペ・参考として記録。

Redmine起動したあと、

# コンテナ内で

entrypoint.sh app:rake redmine:plugins:migrate NAME=redmine_agile VERSION=0 RAILS_ENV=production
rm -rf /home/redmine/data/plugins/redmine_agile

entrypoint.sh app:rake redmine:plugins:migrate NAME=easy_gantt VERSION=0 RAILS_ENV=production
rm -rf /home/redmine/data/plugins/easy_gantt

rm -rf /home/redmine/data/plugins/redmine_tweaks

exit

起動しているコンテナを削除。ここで rm -f redmine までしっかりやらないとコンテナキャッシュの方には上で消したファイルがコピーで残っているので、次に起動するとまた Migration されて復活してしまう

docker-compose stop redmine
docker-compose rm -f redmine

plugin最新化。ここで image: sameersbn/redmine:4.0.3-1 に変更する。

# 作業用にPluginディレクトリを設定。もちろんお好みで。
export _WORK_RED_PLUGINDIR=/path/to/xx

# work_time をgit管理していなかったのでやり直し
cd ${_WORK_RED_PLUGINDIR}
rm -rf redmine_work_time
git clone https://github.com/tkusukawa/redmine_work_time.git

# 使わないので削除
cd ${_WORK_RED_PLUGINDIR}
rm -rf redmine_issue_completion

# 最新化
cd ${_WORK_RED_PLUGINDIR}/view_customize
git stash; git pull
cd ${_WORK_RED_PLUGINDIR}/redmine_default_custom_query
git stash; git pull
cd ${_WORK_RED_PLUGINDIR}/redmine_issue_templates
git stash; git pull
cd ${_WORK_RED_PLUGINDIR}/redmine_issues_summary_graph
git stash; git pull
cd ${_WORK_RED_PLUGINDIR}/redmine_pivot_table
git stash; git pull
cd ${_WORK_RED_PLUGINDIR}/redmine_wiki_extensions
git stash; git pull

# fork先に変更
cd ${_WORK_RED_PLUGINDIR}/clipboard_image_paste
git remote add branch-v4 https://github.com/Utopism/clipboard_image_paste.git
gti remote -v
git pull branch-v4 master

Redmine 4.x 検証まとめ

参考情報に記載したとおり project_settings_tabs まわりのエラーが致命的なので、最後の最後で保留することにしました。プロジェクトの "設定" 画面に繊維する際に 500 エラーが発生してしまうため・・・。ここは別途、時間を見つけて再Tryしようと思います。

Redmine 3.x から 4.x へのアップグレードは Rails 4.x から 5.x への変化を伴い、alias_method_chain という定番コーディング手法が使えなくなったことで Plugin の互換性には大ダメージがあります。そのため Redmine 4.x のリリース後もすぐには飛びつかず様子を見ていて、4ヶ月程経過したのでそろそろよいかなと試すに至ったのですが、惜しいところで終わってしまいました。

上記の通り、メンテナンスされていない Plugin を Redmine 4.x で利用するのはハードルが高いため、ちょっと妥協できるものや代替プラグインがあるものはバッサリ捨てて乗り換えるのが近道なのだろうと思っています。

ちなみに、今回は全く関係ないですが別プロジェクトで活躍しているスクラム開発用プラグイン redmine_backlogsは、Redmine 3.4 へのアップグレード時点で簡単には動かなくなっていたので Redmine 3.3 で止まっている。そのプロジェクトは、この先もずっと Redmine 3.3 から動けなさそうです。

(参考情報)Pluginのアンインストールができない

sameersbn/redmine の README には以下の記載がある。

Uninstalling Plugins

To uninstall plugins you need to first tell redmine about the plugin you need to uninstall. This is done via a rake task:

docker run --name=redmine -it --rm \
  --volume=/srv/docker/redmine/redmine:/home/redmine/data \
  sameersbn/redmine:4.0.3-1 \
  app:rake redmine:plugins:migrate NAME=plugin_name VERSION=0

Once the rake task has been executed, the plugin should be removed from the /srv/docker/redmine/redmine/plugins/ directory.

rm -rf /srv/docker/redmine/redmine/plugins/plugin_name

Any configuration that you may have added in the /srv/docker/redmine/redmine/plugins/post-install.sh script for the plugin should also be removed.

For example, to remove the recurring tasks plugin:

docker run --name=redmine -it --rm \
  --volume=/srv/docker/redmine/redmine:/home/redmine/data \
  sameersbn/redmine:4.0.3-1 \
  app:rake redmine:plugins:migrate NAME=recurring_tasks VERSION=0
rm -rf /srv/docker/redmine/redmine/plugins/recurring_tasks

Now when the image is started the plugin will be gone.

via GitHub - sameersbn/docker-redmine: Docker Image for Redmine

しかし、Plugin <PLUGIN_NAME> was not found とエラーが出て実行できない。例えばこのような感じ。

Running raketask redmine:plugins:migrate...
Plugin easy_gantt was not found.

試したこと:

  • 説明通りに docker run で実行

    • DB接続の環境変数は設定してある
  • docker-compose run redmine bash でコンテナ内に入って entrypoint.sh app:rake ... を実行

    • docker-compose は普段サービス提供用で利用していて正常な前提

よくよく見てみると、説明通りだとrake が実行されるタイミングでは /home/redmine/redmine/plugins ディレクトリ内に何も存在していない。しかし、 sameersbn/redmine を普通に起動した際にはこのディレクトリ内にはマウントした plugins が一通り配置されている。これは起動スクリプトでファイル一式をコピーしているのだと思う。

docker-redmine/entrypoint.sh at 5bd6bcdcdeb68ee7fc169e1f44e180821adafd80 · sameersbn/docker-redmine · GitHub (2019/04/21時点の master レポジトリ)

install_plugins() での処理: docker-redmine/functions at 5bd6bcdcdeb68ee7fc169e1f44e180821adafd80 · sameersbn/docker-redmine · GitHub

一方、その下にある app:rake では execute_raketask として実行しているが、中身は以下の通り、ただ bundle exec raks $@ しているだけ。/home/redmine/redmine/plugins にプラグインが存在していない状態で実行しており、その結果 Plugin <PLUGIN_NAME> was not found エラーが出ていると見える。

docker-redmine/functions at 5bd6bcdcdeb68ee7fc169e1f44e180821adafd80 · sameersbn/docker-redmine · GitHub

結果的には アプリケーションとしてのredmineを正常に起動した状態で(Webサービスコンテナが起動している状態で) redmine:plugins:migrate を実行する必要があるということ。実際、"Pluginアンインストール" セクションで記載の通り Migration が正常に完了しました。

(参考情報)super: no superclass method 'project_settings_tabs'

Completed 500 Internal Server Error in 47ms (ActiveRecord: 5.3ms)

ActionView::Template::Error (super: no superclass method `project_settings_tabs' for #<#<Class:0x00000000071b1140>:0x00000000074deea8>):
    1: <h2><%=l(:label_settings)%></h2>
    2:
    3: <%= render_tabs project_settings_tabs %>
    4:
    5: <% html_title(l(:label_settings)) -%>

plugins/redmine_wiki_extensions/lib/wiki_extensions_projects_helper_patch.rb:22:in `project_settings_tabs'
plugins/redmine_default_custom_query/app/patches/helpers/projects_helper_patch.rb:13:in `project_settings_tabs_with_default_query_setting_tab'
plugins/redmine_issue_templates/lib/issue_templates/projects_helper_patch.rb:6:in `project_settings_tabs'
app/views/projects/settings.html.erb:3:in `_app_views_projects_settings_html_erb___1919254735469764375_59610100'
lib/redmine/sudo_mode.rb:63:in `sudo_mode'

1個ずつPluginを外して切り分けた結果、それぞれ単体では動作した。
ちょっと調べてみると下記のIssue(今回利用しているPluginとは全く関係ない)の議論が参考にるのではないかと思う。

それぞれのファイルの対象コードは以下の通り。比較用にメモ。

# redmine_wiki_extensions/lib/wiki_extensions_projects_helper_patch.rb:22
module ProjectsHelperMethodsWikiExtensions
  def project_settings_tabs
    tabs = super #line 22
# redmine_issue_templates/lib/issue_templates/projects_helper_patch.rb:6
module ProjectsHelperPatch
  def project_settings_tabs
    tabs = super #line 6
# redmine_default_custom_query/app/patches/helpers/projects_helper_patch.rb:13
included do
  alias_method :project_settings_tabs_without_default_query_setting_tab, :project_settings_tabs
  alias_method :project_settings_tabs, :project_settings_tabs_with_default_query_setting_tab
end

def project_settings_tabs_with_default_query_setting_tab
  tabs = project_settings_tabs_without_default_query_setting_tab #line 13

(参考情報)Creating scope :system. Overwriting existing method Enumeration.system.

Creating scope :system. Overwriting existing method Enumeration.system.
Creating scope :sorted. Overwriting existing method Group.sorted.
Creating scope :sorted. Overwriting existing method User.sorted.

エラーの背景はQiitaの記事に関連したものと思うのだが、ググってみると割と数年前から出てる記載も見つかるし、出ているからといって問題になるわけでもない。けど 3.x 系では表示されていないのが気になる・・・。しばし様子見。

WordPress運用をKubernetes(GKE)へ移行する流れ

Kubernetesの利用頻度が増えてきたので、個人VPSで運営しているWordPressをGoogle Kubernetes Engine (GKE)に移行しようと思いました。新規で構築する手順は公式のチュートリアルをはじめまとまっている情報を見かけるのですが、既存の運営データを引っ越す過程から記載している情報を目にしなかったので、それらも記載しています。

これまでのVPS運営に具体的な問題があるわけではないのですが、仕事とは別に個人で運用することで新たに気づく事も多いので、意味のあるトライと位置づけています。

本記事の前提

  • Dockerに関する多少の理解があること(コンテナ、イメージ、タグなどの意味がわかること)
  • GCPでGKEセットアップが完了していること(GKEセットアップ手順は記載していません)
    • kubectl get node にてクラスタノードが確認できればOK
  • データベースはCloud SQL (MySQL)を利用します
  • プライベート運用のお遊びリソースなので、コストはできるだけ安くしたい。そのため、以下の通りKubernetesベストプラクティス構成と比べて若干のイレギュラーがあります
    • GKE(広義でのKubernetesという意味も含む)におけるサービスのパブリック公開・ロードバランスは通常、Ingressリソースが使われますが本構成では利用していません。そのため、作成手順についても触れていません。
      (GKEの nodePort に対して、always free枠のインスタンスにNginxをセットアップしてReverse Proxyしています。この構成は別途記事にしたいと思っている)
    • 負荷の高くないWordPressサイトなので、シングルコンテナでの運用としています。トラフィックに対する可用性は非常に低いです。(但し、Kubernetesオーケストレーションによる障害復帰があるのでNode故障などでの手間はかからないはず)
  • WordPressコンテナイメージは Docker Official Imagesのwordpress を利用します
    • 最初はコンテナ構成にNginxを利用予定でしたが、不都合があり最終的にはApacheとしました。詳細は後述
  • [要注意] GKEクラスタの作成時に --enable-ip-alias オプションを有効にしています。この有無による影響の正確な確認・検証はしていないのですが、ONとする必要があるかもしれません。(Ingressによるサービス提供ならば関係ありません)
    • IP Aliasについては、ドキュメントに記載の通り、Podに対してVPCネットワーク内から疎通できるようになるオプションです。間もなくデフォルトになる旨がWebコンソール画面に記載されています(2019/01/05時点はまだ)。

記事の流れ

  1. WordPressイメージの特徴を把握する
  2. 既存WordPressのデータを移行する
    • サイトのデータ(*.phpなど)を移行する
    • MySQLのデータを移行する
  3. GKEにWordPressをデプロイする
  4. Nginx(別ホスト)経由で Reverse Proxy してインターネットに公開する

目次

WordPressイメージの特徴を把握する

wordpressのレポジトリには多数のタグが並んでいますが、5.0.2-php7.1-apache を例に見ていきます。多少バージョンが違っても考え方は同じなようです。

  • Dockerfile: wordpress/Dockerfile at f63c0db6593494e8fee0e542af9413e22c1da15b · docker-library/wordpress

    • 序盤はApacheのセットアップなので、とりあえず気にする必要はないでしょう
    • L49-55にて指定WordPressのソースを取得し、/usr/src/wordpressに配置しています
    • 最終行で、apache2-foreground のコマンドを ENTRYPOINT であるdocker-entrypoint.shに渡しています
  • docker-entrypoint.sh: wordpress/docker-entrypoint.sh at f63c0db6593494e8fee0e542af9413e22c1da15b · docker-library/wordpress

    • L26で引数が apache2*php-fpm かを判定しています。L29-41周辺にあるuser/group設定のためのようです。
    • L48では、 index.php および wp-includes/version.php存在しない場合の処理が記載されています。これらは対象ディレクトリにWordPressが配置されているかどうか(≒初期セットアップが完了しているかどうか)をチェックしているようで、存在しない場合はDockerfileで記載のあった/usr/src/wordpress からデータを配置しているようです。
    • L87には TODO の字と共に、以下の記載があります。
      • TODO handle WordPress upgrades magically in the same way, but only if wp-includes/version.php's $wp_version is less than /usr/src/wordpress/wp-includes/version.php's $wp_version

      • upgradeに関する処理を実装しようとしているように見えますが、つまり現時点ではイメージとしてWordPressのupgradeを行う処理はしていないと言えます。これは逆に都合がよくて、WordPressをお使いの方ならお分かりの通りWordPressは優秀なアップグレード機構を持っています。管理画面からボタン1個でアップグレード操作を行うことができますので、現時点はWordPressアップグレードのためにDocker Imageタグを指定し直す必要はないと考えてもよいでしょう。
    • L89以降は環境変数に関する処理が続きます。wp-config.php ファイルの位置を定義するなど気になる処理もありますが、今回要件では無視してもよさそうです。
    • L148,L159周辺に記載されている通り、wp-config.php が存在しない場合は wp-config-sample.php の内容をもとに生成しています。ここで、環境変数に設定した値で置き換える処理が入っていますので、移行にあたってはwp-config.phpを一度消しておくことにします。(移行元のをそのまま利用して値だけ手動で書き換えても問題ないはずです)

だいたい特徴がつかめたので、移行作業を進めます。

Step1 既存WordPressのデータを移行する

事前に準備しておくもの

  • 既存WordPressサイトの公開コンテンツデータ(例:public_html): public_html.tar.gz とする
  • 既存WordPressサイトのMySQLデータ: wordpress.dump.gz とする

1-1 サイトのデータ(.phpなど)を移行する

1-1-1 永続ボリュームの作成

まず、Kubernetesクラスタに永続ディスクのボリュームを用意する必要があります。といってもGCPのコンソールからディスクを作成するのではなく、kubectlコマンドを用いて作成することができます。

Kubernetesにおけるディスクボリュームを作成(要求)する処理が、PersistentVolumeClaim と呼ばれるリソースです。以下のコンフィグ(yml)を kubectl apply -f pvc.yml として作成します(PVC = PersistentVolumeClaimの略)。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-disk
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

実行結果:

# WordPressデプロイ先のネームスペースを作成。`wordpress` として作る。
$ kubectl create namespace wordpress
namespace "wordpress" created


# PVCを作成。`-n` で操作対象のネームスペースを指定する。
$ kubectl -n wordpress apply -f pvc.yml
persistentvolumeclaim "wordpress-disk" created


# 作成されたPVCを確認する
$ kubectl -n wordpress get pvc
NAME             STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
wordpress-disk   Bound     pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX   1Gi        RWO            standard       1X

# PVCにより作成された永続ボリュームの実態(PersistentVolume = PV)を確認する
$ kubectl -n wordpress get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                      STORAGECLASS   REASON    AGE
pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX   1Gi        RWO            Delete           Bound     wordpress/wordpress-disk   standard                 1X

ポイント:

  • ReadWriteOnce で作成していますので、1つのコンテナからしかマウントできません。複数コンテナでスケールする必要がある場合は、ReadWriteMany なボリューム(NFSサービスなど)を作成する必要があります。
  • kind: StorageClass を作成しない場合は標準HDDで作成されます。SSDが必要な場合は StorageClass から作成する必要がありますが、今回は扱いません。必要な場合はこちらを参照してください:Storage Classes - Kubernetes
  • コンテンツ量が大きくないので最小のストレージサイズを要求しています。500Miで指定したところ作成された実態は1GiBボリュームでしたので、このコンフィグでも 1Gi としています。大量のコンテンツを保有している場合は、適当なサイズに変更してください。
  • PVのRECLAIM POLICYDeleteとなっています。危ない!!このままだとPVCを削除したときにボリュームが消えてしまいます=コンテンツが消失します。なかなか消すことはないと思いますが、念の為消えないように修正しておきます(下記参照)。

RECLAIM POLICY の修正:

# RECLAIM POLICY を Delete から Retain に変更する
$ kubectl -n wordpress patch pv pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX -p '{"spec": {"persistentVolumeReclaimPolicy":"Retain"}}'
persistentvolume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" patched
  • patch に続くサブコマンドは pv です(pvcではありません!)ので注意
    • 改めて get pv することで変更されていることを確認できるはずです

1-1-2 移行用の一時的なコンテナの作成

さて、先のステップで作成したボリュームにデータをコピーします。このあと出てくるwordpresイメージのデプロイ前にはデータを用意しておきたいので、データ移行用の一時的なPodを作成して kubectl cp コマンドにて配置することとします。(他に良いやり方があれば教えていただけると嬉しいです!)

データ移行用のPodはbusyboxコンテナにて作成します。busyboxはサイズが1MB程度の非常にコンパクトなイメージですので、このような用途にはもってこいです。コンフィグは以下の通り。

datacopy-pod.yml:

apiVersion: v1
kind: Pod
metadata:
  name: data-copy
spec:
  containers:
    - name: sleep-pod
      image: busybox
      command:
      - sleep
      - "3600"
      volumeMounts:
      - name: disk
        mountPath: "/tmp/wp-disk"
  volumes:
    - name: disk
      persistentVolumeClaim:
        claimName: wordpress-disk
  • 見ての通り busybox を sleep 3600 として起動しているのみです。1時間もあればデータ移行作業終わるでしょう、ということでの数字ですが、もっと時間がほしい場合は調整してください。
  • 先程作成したPVC(ディスク)を /tmp/wp-disk のパスにマウントしています。つまり、持っていきたいデータは最終的にここに配置すればOKです。この後の wordpress イメージでは改めて別の path にマウントしますので、ここは適当で構いません。
# Podの作成
$ kubectl -n wordpress apply -f datacopy-pod.yml
pod "data-copy" created

$ kubectl -n wordpress get all
NAME            READY     STATUS    RESTARTS   AGE
pod/data-copy   1/1       Running   0          18s

# 持っていくコンテンツを public_html.tar.gz として、コンテナにコピー `cp` する
$ kubectl -n wordpress cp ./public_html.tar.gz data-copy:/tmp
# 転送完了後、レスポンスメッセージは特になし。

# データ移行用コンテナにINする
$ kubectl -n wordpress exec -it data-copy sh
/ #

# データがあることを確認しましょう。
$ cd /tmp/
$ ls -al

# ...その後、よしなにデータを配置します
# Ownerにも注意しましょう。Apacheの場合は www-data (id 33) です

# 前述の通り、`wp-config.php` は自動生成するので一旦削除(あるいはリネームで退避)しておきます!!

$ eixt

自動生成される wp-config.php の情報源は wp-config-sample.php ですので、ボリュームに無い場合は配置しておきましょう。
WordPress/wp-config-sample.php at master · WordPress/WordPress

コピーが済んだらデータ移行用Podは削除します。

$ kubectl -n wordpress delete -f datacopy-pod.yml
pod "data-copy" deleted

$ kubectl -n wordpress get all
NAME            READY     STATUS        RESTARTS   AGE
pod/data-copy   1/1       Terminating   0          5m
                          # Terminatingの後しばらく放っておくと消えます

ここまでで wordpress コンテンツのデータ移行は完了しました。

1-2 MySQLのデータを移行する

続いて MySQL データを移行します。まず、CloudSQLに対してどのように接続するのか?を考える必要があります。手段はいくつか考えられますが、今回あまりシビアなデータ移行は求めていない & mysqldump サイズがさほど大きくない(100MB以内)ということで、ローカルの macOS から Cloud SQL Proxy 経由でインポートすることとします。(要は、ローカル環境からインターネット経由での mysql 接続となります)

Cloud SQL Proxy とは何ぞや?という方はこちらのドキュメントを参照ください。
Cloud SQL Proxy について  |  Cloud SQL for MySQL  |  Google Cloud

Cloud SQL Proxy を使用して Cloud SQL インスタンスにアクセスする利点は以下のとおりです。

  • 安全な接続: プロキシは、TLS 1.2 と 128 ビット AES 暗号を使用して、データベースとの間で送受信されるトラフィックを自動的に暗号化します。クライアントとサーバーの ID の確認には、SSL 証明書が使用されます。
  • 簡単な接続管理: プロキシが Cloud SQL との認証を処理するので、静的な IP アドレスを提供する必要がなくなります。

MySQLサービスまでの安全な接続経路を担保してくれるもの、ということですね。

Cloud SQL を利用しない場合はVM上での作成あるいはコンテナでのMySQL作成になると思いますが、普通にmysqlコマンドで実施してもらえればよいので、Cloud SQL Proxyの手順は不要です。

1-2-1 Cloud SQL Proxy コンテナの起動

用意するもの:

接続構成概要は以下の通り。

f:id:dobassy:20190104031508p:plain
Cloud SQL Proxy の仕組み

引用:Cloud SQL Proxy について  |  Cloud SQL for MySQL  |  Google Cloud

準備ができたら mysqldump ファイルを有する環境(今回はローカルマシン)にて、以下のコマンドで Cloud SQL Proxy を立ち上げます。

$ docker run -it \
  -v /path/to/keyfile.json:/config \
  -p 127.0.0.1:3306:3306 \
  gcr.io/cloudsql-docker/gce-proxy:1.12 /cloud_sql_proxy \
  -instances=<INSTANCE CONNECTION NAME>=tcp:0.0.0.0:3306 -credential_file=/config

 ...
 2019/01/03 06:35:59 Listening on 0.0.0.0:3306 for <INSTANCE CONNECTION NAME>
 2019/01/03 06:35:59 Ready for new connections

/path/to/keyfile.json および <INSTANCE CONNECTION NAME> の部分は正しい文字に置き換えてください。

Readyとなったあとは、127.0.0.1:3306 にバインドしていますので、mysql -u<USER NAME> -p -h127.0.0.1 -P3306 で接続ができるようになっているはずです。

1-2-2 MySQLユーザーの作成

先程立ち上げた Cloud SQL Proxy コンテナはそのままで、別のコンソールで作業を進めます。

続いてアカウントの作成ですが、root権限でよいか・権限を絞りたいかで作業が変わってきます。

  • root権限で良い場合: Cloud SQLのWebコンソールからアカウントを作成すればよいので手っ取り早いです。
  • 権限を狭めたい場合: 通常通りMySQLコマンドを用いて CREATE USER / GRANT 操作をする必要があります。今回、wordpressデータベースへのアクセス権限のみ設定したいのでこちらの手段をとりますが、内容そのものは一般的なものですので詳細説明は省きます。

MySQL ユーザー | Cloud SQL ドキュメント | Google Cloud

$ mysql -uroot -p$ROOT_PASS -h127.0.0.1 -P3306
mysql: [Warning] Using a password on the command line interface can be insecure.

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is XXXXXX
Server version: 5.7.14-google-log (Google)

Copyright (c) 2000, 2017, 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> create database wordpress;
Query OK, 1 row affected (0.12 sec)

mysql> select User,Host from user;
+-----------+-----------+
| User      | Host      |
+-----------+-----------+
| root      | %         |
| mysql.sys | localhost |
+-----------+-----------+
2 rows in set (0.10 sec)


mysql> CREATE USER 'wordpress'@'%' IDENTIFIED BY 'XXXXXXXX';
Query OK, 0 rows affected (0.10 sec)

mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.10 sec)

mysql> SHOW GRANTS FOR 'wordpress'@'%';
+----------------------------------------------------------------------------+
| Grants for wordpress@%                                                     |
+----------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'wordpress'@'%'                                      |
| GRANT ALL PRIVILEGES ON `wordpress`.* TO 'wordpress'@'%' WITH GRANT OPTION |
+----------------------------------------------------------------------------+
2 rows in set (0.10 sec)


mysql> select User,Host from user;
+-----------+-----------+
| User      | Host      |
+-----------+-----------+
| root      | %         |
| wordpress | %         |
| mysql.sys | localhost |
+-----------+-----------+
3 rows in set (0.10 sec)

1-2-3 データのインポート

ここまでくれば、あとはMySQL知識範囲の操作です。

gzcat wordpress.dump.gz | mysql -uwordpress -p$WORDPRESS_PASS -h127.0.0.1 -P3306 wordpress
  • 大量データで時間を要する場合は、コピー元の環境をよく考えましょう。特に、mysqldumpしてからインポートまでの間隔(データ欠損の許容時間)により変わってきます。
    • 今回、移行時間で失うデータは若干のアクセスログ程度なので普通に移行元サイトをAactiveのまま並行作業しています。
    • CloudSQLと同じリージョンのGCEインスタンスからやればレイテンシは小さくなります。
  • macOSからの操作なので、zcact ではなく gzcat を利用しています

インポート後の結果を確認:

mysql> show tables;
+------------------------+
| Tables_in_wordpress    |
+------------------------+
| wp_ak_404_log          |
| wp_commentmeta         |
| wp_comments            |
 ...

Step2 GKEにWordPressをデプロイする

ようやくデータが整いましたので、ここからサービスをデプロイしていきます。

先程は wordpress 5.0.2 のイメージを対象に内容を確認していましたが、アップグレードに関する一連の流れの動作確認をしておきたかったので、移行元サイトで利用していた wordpress 4.9.8 のイメージで作業を進めます。

2-1 wordpress イメージタグの確認

4.9.8 タグのバリエーションを確認します。registry.hub.docker.com のAPIに対するGETリクエストで確認する方法が手軽そうです。

# `jq` はJSONフォーマットを操作する定番コマンドです。入ってない場合は `brew install jq` にて入れておきましょう。
$ curl -s https://registry.hub.docker.com/v1/repositories/wordpress/tags | jq -r '.[].name' | grep 4.9.8

4.9.8
4.9.8-apache
4.9.8-fpm
4.9.8-fpm-alpine
4.9.8-php5.6
4.9.8-php5.6-apache
4.9.8-php5.6-fpm
4.9.8-php5.6-fpm-alpine
4.9.8-php7.0
4.9.8-php7.0-apache
4.9.8-php7.0-fpm
4.9.8-php7.0-fpm-alpine
4.9.8-php7.1
4.9.8-php7.1-apache
4.9.8-php7.1-fpm
4.9.8-php7.1-fpm-alpine
4.9.8-php7.2
4.9.8-php7.2-apache
4.9.8-php7.2-fpm
4.9.8-php7.2-fpm-alpine

元々 Nginx + php-fpm 構成で運用していたので今回 4.9.8-php*.*-fpm-alpine を利用するつもりだったのですが、VolueMountまわりで不都合あり一旦見送り、 *-apache を採用しました。詳細はAppendixにて。

2-2 wordpress リソースコンフィグの作成

先に結論ですが、最終的には以下のコンフィグをKubernetesにデプロイします。

Serviceリソース

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    name: wordpress
spec:
  type: NodePort
  selector:
    app: wordpress
  ports:
  - name: http
    port: 80
    targetPort: 80
    # GKEクラスタ外からWordPressへ接続するためのポート番号 (範囲: 30000-32767 から指定)
    nodePort: 30000

Deploymentリソース

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  strategy:
    # kubectl apply 時の挙動。詳細後述 *1
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0%
      maxUnavailable: 100%
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      # CONTAINER 1: WordPressコンテナ
      - name: wordpress
        image: wordpress:4.9.8-php7.1-apache
        # このコンテナではどの程度の最低リソースを要求するかを記載 *2
        resources:
          requests:
            cpu: 50m
            memory: 200Mi
        env:
        # Cloud SQL に接続するための情報。同一Podなので 127.0.0.1 でアクセスできる
        - name: WORDPRESS_DB_HOST
          value: "127.0.0.1"
        - name: WORDPRESS_DB_NAME
          value: "wordpress"
        # パスワードのような秘匿情報は Secret リソースから取得する(コンフィグに値を直接書かない)
        - name: WORDPRESS_DB_USER
          valueFrom:
            secretKeyRef:
              name: cloudsql-db-credentials
              key: username
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: cloudsql-db-credentials
              key: password
        - name: WORDPRESS_DB_CHARSET
          value: "utf8"
        - name: WORDPRESS_DB_COLLATE
          value: ""
        - name: WORDPRESS_TABLE_PREFIX
          value: "wp_"
        # データ移行用コンテナで準備したボリュームを配置する場所
        volumeMounts:
        - name: disk
          mountPath: "/var/www/html"

      # CONTAINER 2: CLOUD SQLコンテナ
      - name: cloudsql-proxy
        image: gcr.io/cloudsql-docker/gce-proxy:1.11
        command: ["/cloud_sql_proxy",
                  "-instances=<INSTANCE CONNECTION NAME>=tcp:3306",
                  "-credential_file=/secrets/cloudsql/credentials.json"]
        securityContext:
          runAsUser: 2  # non-root user
          allowPrivilegeEscalation: false
        # IAMサービスアカウントのキーファイルをマウントしている。ファイル作成は後述
        volumeMounts:
        - name: cloudsql-instance-credentials
          mountPath: /secrets/cloudsql
          readOnly: true

      volumes:
      - name: disk
        persistentVolumeClaim:
          claimName: wordpress-disk
      - name: cloudsql-instance-credentials
        secret:
          secretName: cloudsql-instance-credentials

2-3 Secret(秘密情報)の準備

コンフィグ内に出てくる3つのキーを作成します。

  • cloudsql-instance-credentials: CloudSQLに接続するためのキーファイル(JSON)
  • cloudsql-db-credentials: MySQL接続に必要な以下の値を格納するSecret
    • username
    • password

KubernetesのSecret系情報を保管する領域に情報を預けるような操作をしていきます。

$ kubectl -n wordpress create secret generic cloudsql-instance-credentials \
    --from-file=credentials.json=/path/to/keyfile.json
    
    # 先の手順で mysqldumpファイルをインポートした時にローカルで利用したものと同じキーファイルを指定する


$ kubectl -n wordpress create secret generic cloudsql-db-credentials \
    --from-literal=username=<string username> --from-literal=password=<string password>
    
    # username / password それぞれの値を記載する。カッコ <> は不要です

作成したら、値が入っていることを確認します。

$  kubectl -n wordpress get secret
NAME                            TYPE                                  DATA      AGE
cloudsql-db-credentials         Opaque                                2         1d
cloudsql-instance-credentials   Opaque                                1         1d

2-4 Service / Deployment の作成

あとは、前述の service.yml および deployment.ymlkubectl apply することで起動してくるはずです。順番はどちらが先でも構いません。

Podが Running になっていることを確認しましょう。
(一瞬 Running になっていてもしばらくして Crash していることもあるので数分間確認しておきましょう。kubectl describe pod XXXXEvents欄に Warning が出ていないことを確認するのも有用です。Crashしてしまった場合は、内容に応じて対処が変わってきます。)

$ kubectl -n wordpress get all
NAME                             READY     STATUS    RESTARTS   AGE
pod/wordpress-XXXXXXXXXX-YYYYY   2/2       Running   0          13h

NAME                TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
service/wordpress   NodePort   10.X.Y.Z      <none>        80:30000/TCP   1d

NAME                        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/wordpress   1         1         1            1           1d

2-5 Nginx(別ホスト)経由で Reverse Proxy してインターネットに公開する

いよいよInternetへ公開となります。普通ならここでIngressリソースによるロードバランサーを作成するところですが、今回は冒頭に記載の通りコスト成約のため、always free枠のGCEインスタンスに構築したNginxを経由します。

まず、KubernetesノードのIPを確認します。

$ kubectl describe node | grep IP
  InternalIP:  10.0.0.4
  ExternalIP:  XX.XXX.XXX.C
  InternalIP:  10.0.0.3
  ExternalIP:  XX.XXX.XXX.B
  InternalIP:  10.0.0.2
  ExternalIP:  XX.XXX.XXX.A
  # 動かしているKubernetesノードの数により返却数は異なる

ここで確認するのは InternalIP の部分です。GCEインスタンス(Nginx)からアクセスするのはVPC内部通信になるため。

  • [要注意] 冒頭で記載した --enable-ip-alias を有効化せずに GKE クラスタ作成をした場合、InternalIP がどうなるのか未確認です。もしかすると、IP宛通信ができないかもしれません。
  • ExternalIP を利用することもできるはずですが、ファイアウォールの設定調整が必要になってくると思われます(未検証)。

続いて、InternalIP:nodePort に対して疎通確認してみましょう。どのIP宛でも問題ありません。

$ curl -I 10.0.0.4:30180
HTTP/1.1 301 Moved Permanently
Server: Apache/2.X.XX (Debian)
X-Powered-By: PHP/7.X.XX
Location: http://10.0.0.4/
Content-Type: text/html; charset=UTF-8

こんな感じでHTTPレスポンスがあればOKです。あとは、Nginxの設定です。

upstream k8snodes {
    least_conn;
    server 10.0.0.2:30000;
    server 10.0.0.3:30000;
    server 10.0.0.4:30000;
}

server {
    server_name your.domai.n;

    listen 443 ssl http2;

    ...中略...
    
    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_pass http://k8snodes;
    }
}

Nginxの設定はお使いの環境に応じて調整してください。

まとめ

Kubernetes上へのWordPressの移行作業が完了しました。正確には、移行元サイトで利用していた Let's Encrypt 証明書の移行などの手順も入ってくるのですが本質ではない話が膨らんでいくので端折っています。

Version 4.9.8 のイメージで移行後、Web管理画面からバージョンアップを試みたところ、最新の 5.0.2 (2019/01/05現在) へのバージョンアップが正常にできることを確認しました。なので、しばらく wordpress イメージを apply し直す必要はないでしょう。バージョンが不一致で気持ち悪い場合、latest タグを利用するのも選択肢ですが、docker-entrypoint.sh の処理がいつ変わるかも分かりませんので、把握できているものに固定化しておく方がよいかとは思います。

今回は求める可用性レベル・少ないKubernetesリソースの有効活用・および作業時間の都合でシングルコンテナ構成となってしまいましたが、いつか ReadWriteMany ボリュームによる複数コンテナ構成も試したいところです。

Appendix

Nginx + php-fpm 構成を諦めた理由

*php-fpm* のコンテナはその名の通り php-fpm しか入っていないので、Webサーバー(Nginx)コンテナが別途必要になります。すると、WordPressのコンテンツファイルは Nginxコンテナ および php-fpmコンテナ 両方に対して必要になってくるのですが、同一ボリュームを複数コンテナからマウントしたらPodが起動できませんでした。Nginxコンテナのボリュームを readOnly: true としましたが結果変わらずです。

Events:
...
Warning  FailedAttachVolume      1m    attachdetach-controller                       Multi-Attach error for volume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" Volume is already used by pod(s) wordpress-XXXXXXXXX-XXXXX
Normal   SuccessfulAttachVolume  47s   attachdetach-controller                       AttachVolume.Attach succeeded for volume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"

となると、ReadWriteManyなボリュームで再設計するか、それか他のうまいやり方を考えるかなのですが・・・。ApacheコンテナならばWebサーバー件PHP実行環境を単一コンテナで実現できるので、一旦 *-apache で回避することにしました。

コンフィグを修正して apply し直してもコンテナが入れ替わらない *1

検証中は何回も kubectl apply -f deployment.yml することになりますが、なかなか新しいPodに切り替わってくれないことがありました。describeEvents:欄を見ると以下の通り Warning が出ています。

Warning  FailedAttachVolume      11m   attachdetach-controller                       Multi-Attach error for volume "pvc-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" Volume is already used by pod(s) wordpress-XXXXXXXXX-XXXXX

入れ替え前のPodがVolumeを掴んだまま新しいPodでVolumeマウントしようとしているため Multi-Attach error が出ているように見えます。デフォルトが RollingUpdate であることがドキュメントに記載されています。

.spec.strategy specifies the strategy used to replace old Pods by new ones. .spec.strategy.type can be “Recreate” or “RollingUpdate”. “RollingUpdate” is the default value.

Deployments - Kubernetes

というわけで Recreate に設定して apply し直したところ、別のエラーが発生。

The Deployment "wordpress" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'

既に RollingUpdate で作成しているのに apply で変更しようとしたために発生したErrorと思われます。一度 delete のうえ改めて create or apply すればうまくいくのだと思いますが、今後スケールアウト設計にする時が来たら改めて RollingUpdate にするわけなので、RollingUpdate のままで Recreate と同様の挙動になる設定としました。それが以下の値です。

  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0%
      maxUnavailable: 100%

strategy については以下のページが分かりやすいです:
Kubernetes道場 8日目 - ReplicaSet / Deploymentについて - Toku's Blog

リソース要求について *2

Deploymentリソースで以下の記載をしています。

  resources:
    requests:
      cpu: 50m
      memory: 200Mi

これらを指定しないでデプロイすると、Kubernetesノードは以下のような内容になります(とある1ノード)。

$  kubectl describe node

Non-terminated Pods:         (6 in total)                                                                                                                                                                          
  Namespace                  Name                                              CPU Requests  CPU Limits  Memory Requests  Memory Limits                                                                            
  ---------                  ----                                              ------------  ----------  ---------------  -------------                                                                            
  kube-system                fluentd-gcp-v3.1.0-7b4jr                          100m (10%)    1 (106%)    200Mi (17%)      500Mi (43%)                                                                              
  kube-system                heapster-v1.6.0-beta.1-155fb8fd97-pafmt           138m (14%)    138m (14%)  301856Ki (25%)   301856Ki (25%)                                                                           
  kube-system                kube-dns-148976df6c-ra98z                         260m (27%)    0 (0%)      110Mi (9%)       170Mi (14%)                                                                              
  kube-system                kube-proxy-gke-default-pool-b76614f2-8agq         100m (10%)    0 (0%)      0 (0%)           0 (0%)                                                                                   
  wordpress                  wordpress-XXXXXXXXX-XXXXX                         0 (0%)        0 (0%)      0 (0%)           0 (0%)  

最終行の wordpress を見てみると、一律で 0 になっている状態です。指定しなくても動くときは動くのですが、Kubernetesリソースがギリギリの状況では不都合な挙動になると思いますので、不都合を理解のうえ設定しましょう。詳細は以下のページが詳しいです。

dr-asa.hatenablog.com

それにしても、fluentdの要求するリソースがちょっと大きいですね・・・。ギリギリコストのインスタンススペックで運用しようと思うと存在感が大きいです。

*phpX.Y* に関する選定基準

移行元サイトでPHP5.Xを利用していましたが、今回PHP7.Xのイメージに使うに当たり、簡単に(超雑に)abでテストしました。

実行環境は以下の通り。

  • v1.11.5-gke.5
  • Container-Optimized OS from Google
  • instance-type=g1-small
  • 負荷元: 日本
  • 負荷先: 米国リージョン

今回 php7.1 の方が良かったのでこれを選びましたが、試行回数も同一条件で2回程度、同じ傾向なのを確認したうえですがサンプルが少なすぎるのであくまで雰囲気レベルで見てください。

wordpress:4.9.8-php7.1-apache:

Concurrency Level:      4
Complete requests:      100
Failed requests:        0
Write errors:           0
Requests per second:    5.59 [#/sec] (mean)
Transfer rate:          328.74 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      287  291   4.6    290     321
Processing:   355  397  58.2    373     646
Waiting:      261  299  54.6    277     552
Total:        644  688  60.3    663     936

wordpress:4.9.8-php7.2-apache:

Concurrency Level:      4
Complete requests:      100
Failed requests:        0
Write errors:           0
Requests per second:    3.76 [#/sec] (mean)
Transfer rate:          220.89 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      287  292   5.5    290     320
Processing:   364  714 925.1    551    7613
Waiting:      269  478 613.2    399    5724
Total:        653 1006 927.0    843    7920

Telegraf を CoreOS にインストールしてメトリクスを取得する方法

次の記事で Ubuntu 14.04 に Telegraf をインストールしましたが、今回は CoreOS へインストールします。インストール時点でのバージョンは 1122.3.0 となります。

blog.exlair.net

Telegraf バイナリの取得

GitHub - influxdata/telegraf: The plugin-driven server agent for collecting & reporting metrics.

Linux tarballs: のセクションから入手してください。

https://dl.influxdata.com/telegraf/releases/telegraf-1.0.1_linux_amd64.tar.gz
(2016/10/24時点のバージョン)

インストール時は、最新バージョンをご確認ください。tarball を展開すると以下の構成になっています。

etc/logrotate.d/telegraf      # log rotate config
etc/telegraf/telegraf.conf    # config

var/log/telegraf/             # 空ディレクトリ

usr/bin/telegraf              # 実行バイナリ本体
usr/lib/telegraf/scripts/
                        init.sh            # initスクリプト
                        telegraf.service   # systemd の設定ファイル

そのまま / に上書きしようとすると怒られます。

sudo mv usr/bin/telegraf /usr/bin/
mv: inter-device move failed: 'usr/bin/telegraf' to '/usr/bin/telegraf'; unable to remove target: Read-only file system

CoreOSではユーザーが書き込みできる領域が限られているためです。CoreOSでは標準で$PATH/opt/binが含まれていますので、ここに配置します。

ここで欲しいファイルは2つなので、手動で設定してしまうことにします。

sudo mv usr/bin/telegraf /opt/bin/
sudo mv usr/lib/telegraf/scripts/telegraf.service /etc/systemd/system/

sudo mkdir -p /var/log/telegraf
sudo mkdir /etc/telegraf
sudo mkdir /etc/telegraf/telegraf.d
sudo vi /etc/telegraf/telegraf.conf

service ファイルの修正

telegraf の配置パスを変更したので一部修正します。

本記事ではユーザーを作成していませんので、ここは必要に応じて適宜見直しください。

--- telegraf.service 2016-10-24 02:27:01.000000000 +0900
+++ /etc/systemd/system/telegraf.service  2016-10-24 02:32:10.000000000 +0900
@@ -5,10 +5,10 @@
 
 [Service]
 EnvironmentFile=-/etc/default/telegraf
-User=telegraf
+User=root
 Environment='STDOUT=/var/log/telegraf/telegraf.log'
 Environment='STDERR=/var/log/telegraf/telegraf.log'
-ExecStart=/bin/sh -c "exec /usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d ${TELEGRAF_OPTS} >>${STDOUT} 2>>${STDERR}"
+ExecStart=/bin/sh -c "exec /opt/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d ${TELEGRAF_OPTS} >>${STDOUT} 2>>${STDERR}"
 ExecReload=/bin/kill -HUP $MAINPID
 Restart=on-failure
 KillMode=control-group

systemd で Telegrafサービスを有効にします。

$ sudo systemctl enable telegraf

$ systemctl list-unit-files telegraf.service
UNIT FILE        STATE  
telegraf.service enabled

最後に、enabled になっていることを確認したら sudo systemctl start telegraf で完成です。もし起動しない場合、sudo journalctl -u telegraf -f で起動ログを確認しながら動作確認を試みるとヒントが見つかるはずです。

意外と、InfluxDB側のHTTPサーバーのアクセス制限だったりしますので気をつけましょう。
(別サーバーだったりする場合は特に!)

Telegrafでサバーリソースを取得してInfluxDBに格納する

Telegraf は、InfluxDBと相性のよいメトリクスの収集ツールです。

Telegraf is an agent written in Go for collecting metrics from the system it's running on, or from other services, and writing them into InfluxDB

GitHub - influxdata/telegraf: The plugin-driven server agent for collecting & reporting metrics.

これまではZabbixとGrafana、(および、一部InfluxDB)を利用してサーバーリソース等のメトリクス表示をしていました。Zabbix Agent から取得したデータでもきちんとGrafanaにて表示できるので不自由はないのですが、ZabbixのDatabase (MySQL) から長期間・複数の情報を取得しようとすると、RDBMSの特性かどうしてもレスポンスが悪くなってしまっていました。

それならば、サーバーリソース系の情報もInfluxDBに格納してみようではないかと思ったのが動機です。

f:id:exlair:20161024011044p:plain

前提

  • InfluxDB を利用できる環境が整っていること
    環境がない場合、Dockerを利用するのが手軽です。Docker Hub - influxdb official repoxitory
  • Database は create database telegraf として作れているものとします
  • 本記事では Ubuntu 14.04 としています。16.04やRedHat系の場合、rcスクリプトが同じように操作できるかはわかりません(systemdでの操作になるので、できないと思います)

Telegraf エージェントのインストール

curl -LO https://dl.influxdata.com/telegraf/releases/telegraf_1.0.1_amd64.deb
sudo dpkg -i telegraf_1.0.1_amd64.deb 

(Reading database ... 143760 files and directories currently installed.)
Preparing to unpack telegraf_1.0.1_amd64.deb ...
Unpacking telegraf (1.0.1-1) ...
Setting up telegraf (1.0.1-1) ...
 Adding system startup for /etc/init.d/telegraf ...
   /etc/rc0.d/K20telegraf -> ../init.d/telegraf
   /etc/rc1.d/K20telegraf -> ../init.d/telegraf
   /etc/rc6.d/K20telegraf -> ../init.d/telegraf
   /etc/rc2.d/S20telegraf -> ../init.d/telegraf
   /etc/rc3.d/S20telegraf -> ../init.d/telegraf
   /etc/rc4.d/S20telegraf -> ../init.d/telegraf
   /etc/rc5.d/S20telegraf -> ../init.d/telegraf
telegraf process is not running [ FAILED ]
Starting the process telegraf [ OK ]
telegraf process was started [ OK ]

FAILED となっている部分が若干気になりますが、起動はしているようなので問題無さそうです。

ps -ef | grep tele
telegraf  ..中略.. /usr/bin/telegraf -pidfile /var/run/telegraf/telegraf.pid -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d

標準の設定(2016/10/23時点)は以下コンフィグ /etc/telegraf/telegraf.conf の状態で起動されました。

[global_tags]
[agent]
  interval = "10s"
  round_interval = true
  metric_batch_size = 1000
  metric_buffer_limit = 10000
  collection_jitter = "0s"
  flush_interval = "10s"
  flush_jitter = "0s"
  precision = ""
  debug = false
  quiet = false
  hostname = ""
  omit_hostname = false
[[outputs.influxdb]]
  urls = ["http://localhost:8086"] # required
  database = "telegraf" # required
  retention_policy = ""
  write_consistency = "any"
  timeout = "5s"
[[inputs.cpu]]
  percpu = true
  totalcpu = true
  fielddrop = ["time_*"]
[[inputs.disk]]
  ignore_fs = ["tmpfs", "devtmpfs"]
[[inputs.diskio]]
[[inputs.kernel]]
[[inputs.mem]]
[[inputs.processes]]
[[inputs.swap]]
[[inputs.system]]

一旦終了しておきます。

sudo /etc/init.d/telegraf stop
--> telegraf process was stopped [ OK ]

コンフィグ例

コンフィグにあたってはいくつかのサイトを参考にしたところ、バージョンによる理由か(?)記載方法が古かったりして情報が錯綜していたので、素直に本家プロジェクトサイトのコンフィグ例を参照するのがよさそうです。

telegraf/CONFIGURATION.md at master · influxdata/telegraf · GitHub

まず今回は、上記標準コンフィグのうち InfluxDB のURLのみ書き換えてそのまま利用しました。

[[outputs.influxdb]]
  urls = ["http://yuor-url:your-port"] # ここ。ローカル起動なら localhost で良いはず
  database = "telegraf" # required

起動テストが可能です。間違っていなければ、現在の値っぽい情報が表示されます。

telegraf -config /etc/telegraf/telegraf.conf -test

Telegraf の起動

RCスクリプトを利用して起動します。

sudo /etc/init.d/telegraf start

sudo /etc/init.d/telegraf status

なお、

sudo telegraf -config telegraf.conf

このようにも起動可能です。
(RCスクリプトのstartではこのように実行されています。もっとたくさんの設定がありますが)

取得されたデータ

f:id:exlair:20161024011111p:plain

cpu,cpu=cpu-total,host=xxxhostxxx
cpu,cpu=cpu0,host=xxxhostxxx
cpu,cpu=cpu1,host=xxxhostxxx
disk,fstype=ext2,host=xxxhostxxx,path=/boot
disk,fstype=ext4,host=xxxhostxxx,path=/
diskio,host=xxxhostxxx,name=dm-0
diskio,host=xxxhostxxx,name=dm-1
diskio,host=xxxhostxxx,name=vda
diskio,host=xxxhostxxx,name=vda1
diskio,host=xxxhostxxx,name=vda2
diskio,host=xxxhostxxx,name=vda5
kernel,host=xxxhostxxx
mem,host=xxxhostxxx
processes,host=xxxhostxxx
swap,host=xxxhostxxx
system,host=xxxhostxxx

host の文字列は置き換えてあります。

例えば、cpu-total からは以下の数値を取得可能です。

 cpu            host
    usage_guest    usage_guest_nice
    usage_idle     usage_iowait      usage_irq
    usage_nice     usage_softirq     usage_steal
    usage_system   usage_user

詳細は telegraf/CPU_README.md at master · influxdata/telegraf · GitHub より。

同様に、
Supported Input Plugins · influxdata/telegraf · GitHub
セクションに記載されているリンクから各種詳細を取得することができるようです。

ここまでくれば、あとは普段の使い方でGrafanaから数値を取得できるはずです。

動画ファイルをPS3経由のテレビでお手軽再生したい (Universal Media Server)

Universal Media Server とは

Universal Media Server (UMS) は、パソコンやサーバーに格納している動画ファイルを、PS3経由でテレビ視聴するためのソフトウェアです。

タイトルはPS3としていますが、技術はDLNAという標準仕様ですのでPS4やその他DLNA対応機器で閲覧できるはずです。現在、自分の環境ではPS3しか手元にないのでこのようなタイトル設定をしています。おそらく多くの動画ファイルフォーマットに対応しているのですが、近年は iPhone で撮影した動画をはじめmp4ファイルばかり扱っていますので、他のコーデックでどう動くか確認不足です。

似たようなソフトウェアに PS3 Media Server (PMS) というものがあり、ここ数年間はこれを用いて自宅での動画視聴環境を整えていましたが、PMSの開発は随分前に終了してしまっています。とはいえ、"動画ファイル(mp4ファイル)を視聴する" というだけの目的であれば開発終了した PMS でも必要十分だったので特に不満なく使い続けてきました。

そうしているうちに UMS として名前を変え、別プロジェクトでオープンソース化されていました。今回、自宅サーバー老朽化に伴いソフトウェア構成を見直しているので、どうせならば最新化しようということで選定してみることにします。

Media Server の比較資料があります: Comparison of Media Servers

UMSの方が圧倒的に充実してはいるのですが、自身の利用目的は所詮 "mp4ファイルを視聴したい" のみなので、そうなると変化があまり感じられないのが本音。まだ構築直後で大量にある UMS の設定項目も十分把握できていないため、もう少し様子見したいと思います。

UMS実行環境の構築方法

ここでは、Linuxサーバー内にある動画ファイルをDLNA配信する目的で構築します。

なお、Windows や macOS に保存してある動画ファイルを配信したい場合、各OS向けのバイナリも提供されていますので 同様のことが可能です。GUIツールが付属するはずですので、より手軽ではないかと思います(試していません)。

起動までの手順は(こちらに記載の通り UniversalMediaServer/INSTALL.txt)、

  1. JVM および メディア関連のパッケージをインストールする
  2. UMSをダウンロードする
  3. tgzを展開する
  4. コンフィグを修正する
  5. 格納されている ./UMS.sh で起動する

これだけなのですが、個人的にはメディア系のソフトウェアを多用途サーバーにガッツリ混在させたくないことや、起動スクリプトの準備が面倒くさいといった理由から、Dockerコンテナで動かすことにしました。

作成したコンテナは GitHub に作成済みです。
GitHub - dobassy/docker-ums: Dockerfile to build a Universal Media Server (UMS) container image.

Docker-UMS 動作仕様のポイント

詳細はレポジトリのREADMEに記載の通りですので割愛します。起動しているホストのNICが必ずしも eth0 ではないので、うまく動作しない場合は疑ってみてください。

先の記事(*1)で Netatalk によるファイルサーバーを作成しましたが、ここで共有するディレクトリと、今回のUMSがDLNA配信する際に参照するファイル群のディレクトリを合わせておくと、テレビで観たい動画があれば Finder からコピーして手軽に大画面視聴できるかと思います。

DLNA配信に利用するメディアファイルのパス指定は、環境変数 X_UMS_FOLDERS にて設定することができます。

(*1)

blog.exlair.net

Entrykit の採用による環境変数活用の促進

UMSの設定は環境により差が生じやすいので、docker run 時の環境変数でチューニングできるようにしたい思いがありました。よくあるコンテナ作成テクニックとして、コンテナ起動時の初期化スクリプトで sed して置換をがんばる秘伝のタレをよく見かけますが、これは面倒くさい…

そんななか、たまたま Entrykit (Entrypoint tools for elegant, programmable containers) というプログラムに出会いました。何ができるのかというと、このまとめ紹介記事がとてもわかり易いです。Entrykit のすすめ - Qiita

設定ファイルを任意に変数化でき、環境変数の有無に応じて上書き or デフォルト値の反映が柔軟に行えるようになります。これがあれば、シンプルな用途であれば秘伝のタレスクリプトを作らなくてもよくなりそうですね。

では、よい動画視聴ライフを。

DockerでmacOSファイル共有サーバをお手軽構築

自宅のサーバーが古くなり、いつ故障してもおかしくない状況なので少しずつ移行をはじめています。新サーバーでは原則ルールとして Docker コンテナでの再構築に取り組んでいるため、それぞれの機能をメモしていこうと思います。

今回は Netatalk について。

Netatalk とは

Netatalk - Wikipedia

netatalkUnix系OS上でMac OSOS Xに対してAFPによるファイルサーバの機能を提供するオープンソースのソフトウェアである。 かつてはAppleTalkも実装したが、この機能は削除された。

MacのローカルファイルシステムであるHFSやHFS+は、リソースフォークやFinder情報といったメタデータをもつ。更に最近のOS Xでは拡張属性 (EA)も扱う。netatalkは、こうしたApple独自のメタデータを完全に保存することを目的として設計されたファイルサーバソフトウェアである。 メタデータは可能な限りサーバ側のファイルシステムの拡張属性に保存する。これが出来ない場合は隠しファイルを作って保存する。 また、バックアップソフトウェアであるTime Machineの保存先として利用することもできる。

引用の通り、macOS 向けのファイル共有プロトコルです。Mac は賢いので Windows の共有フォルダ(SMB)も普通に使えるのですが、.DS_Store といったゴミファイルが見えてしまったりと煩わしい点も若干あるので、より純粋なファイル共有プロトコルを採用します。

余談になりますが、自宅には一応Windowsもあるので、結局は Samba も構築することになります。

Docker Image の選定

ここでは Docker そのものの利用について触れません。docker rundocker-compose up のコマンドを用いたことがある方を想定しています。

Image を Dockerfile で新規構築してもよいのですが、先人たちの優れたコンテナが登録されていることも多々あるため、まずは DockerHub で検索します。環境変数での設定可能箇所が多いほど、柔軟で使いやすいコンテナであることが多いです。

❯ docker search netatalk
INDEX       NAME                                            DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
docker.io   docker.io/odarriba/timemachine                  Lastest Netatalk with configurable user/pa...   22                   [OK]
docker.io   docker.io/cptactionhank/netatalk                                                                13                   [OK]
docker.io   docker.io/cilix/netatalk                        Docker image designed to make it easy to h...   1                    [OK]
docker.io   docker.io/ironicbadger/build-netatalk3-debian   Builds v3 of netatalk for Debian from source    1                    [OK]
docker.io   docker.io/r1kelsey/netatalk                                                                     1                    [OK]
docker.io   docker.io/chattaway/netatalk                    netatalk                                        0                    [OK]

(2016/09/17 18:00現在)

Stars の数が一定数あるものを選びます。必ずしも Star が多からよいというわけではなく、少ないものでも日が浅いだけで高品質なものが埋もれている可能性はありますが、品質を測る一定の目安にはなるはずです。

一番多い odarriba/timemachine は timemachine 目的に特化しているみたいなので、今回選定対象からは外しました。そして選択したのがこちら。

cptactionhank/netatalk

下記の通り docker-compose.yml を作成したところ、正常に動作しました。

docker-compose.yml:

version: '2'

services:
  netatalk:
    image: cptactionhank/netatalk:latest
    container_name: netatalk
    network_mode: host
    env_file: credential.env #パスワード系のファイルは外部に
    environment:
      - AVAHI=1
    ports:
      - "548:548"
    volumes:
      - /your/media/path:/media/share
    restart: always

credential.env (docker-compose.yml の中に書いても構いません):

AFP_USER=username
AFP_PASSWORD=password
AFP_UID=1000
AFP_GID=1000

Netatalk 動作仕様の簡単なメモ

  • デフォルト設定 (afp.conf) では、 /media/share のみが共有されるようになっています
  • 1組(のみ?)のユーザーID/パスワードを環境変数で設定しなければ動作しません
  • 標準設定では不足する場合、/etc/afp.conf を volume mount (-v) で置換することで自由に設定可能です

凝った使い方をしなければこれだけで十分な気がします。

コンテナ設定のポイント

  • 環境変数にて AVAHI=1 を設定すると Auto Discovery が有効になります。Finderの左メニューに「共有」として自動検出されるものです。
  • network_mode: host オプションにて、ホストマシンの Interface に bind します。これをせずに通常の起動でコンテナの仮想NICに bind した場合、avahi による Auto Discovery が機能しません。
    • この時、Finderから「サーバへ接続(Command + K)」を選択してホストマシンのIPアドレス指定 (afp://IP_Address/) すれば接続することはできますが不便です。

まとめ

DockerHub のイメージを利用して簡単に Mac ファイル共有サーバーを作成しました。なお、この方法で作成したネットワークパスを TimeMacahine 保存場所としても利用できるはずです。

参考リンク

GitHub - cptactionhank/docker-netatalk: Docker container running Netatalk

Mac OS X に Rails &amp; Redmine 環境を構築して Plugin 開発をする準備

はじめに

仕事で Redmine を利用しており、あれこれカスタマイズ・活用していると欲がでてくるもので、求める Plugin が見つからないと自ら開発したくなってきます。しかし、Rails 環境をまともに構築したことがないので、開発環境の準備方法すら分からない... ということで、何も考えずに RailsRedmine が動く ローカル環境を準備することに特化した作業手順を記載します。

完全新規からのセットアップではないため若干端折ってしまっていますが、そこは前提を丁寧に記載することでフォローしたいと思います。

本エントリ記載手順の前提環境

  • Mac OS X El Capitan - version 10.11.4
  • rbenv (ローカル ruby 実行バージョンを簡単に切り替えできる支援ツール) インストール済み
  • ruby -v
    ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-darwin15]
  • brew -v
    Homebrew 0.9.9 (git revision 2f20; last commit 2016-05-15)
    Homebrew/homebrew-core (git revision 0a6d; last commit 2016-05-16)

今回用いた Mac では、既に rbenv と brew をインストール済みでした。ruby の2.2系がどういう位置づけなのかサッパリ把握していませんが、今回の作業実施にあたり参照したブログ記事で2.2系をいくつか見たという理由と、仕事で用いている RedmineのDockerコンテナ では最新版で 2.1.7-p400 (2015-08-18) が使われており、まあ近いでしょう、というあまり根拠になっていない理由で良しとしました。

rbenv と brew (Homebrew) の準備は下記リンクを参考にしてください。

余談その1:
Redmine をサクッと立ち上げたい場合はこちらのDockerコンテナが非常に強力です。docker-compose が動く環境さえあれば(もちろん、普通に docker run してもよいのですが) 、新規環境をサクサクと作成することができます。仕事では、事業部門毎にひとつ Redmine の動作環境を与えるイメージで切り出しをしています。今回のメインテーマではないので紹介はこの程度にします。
sameersbn/docker-redmine: Dockerized redmine app server

Rails 実行環境の構築

では早速、環境作りを勧めます。以下コマンドを何も考えずに打ち込みます。

$ gem install powder
$ rbenv rehash
$ powder install

pwder とは

powは手軽に使えるRackサーバです。 Rackアプリ、Railsアプリの開発に便利です。

Mac用のRackサーバ「pow」が便利 - Qiita

ow + rbenvで手軽なRack環境構築 MacOSXでRack環境をlocalに手軽に準備するものとして pow があるのですが

pow + rbenvで手軽なRack環境構築 - Qiita

正直何者かよく理解していないわけですが、先人たちが OS X での Rack 環境構築には便利なものと推されているので素直に従います。

そういえば、上記引用リンクの先には node が必要という記載もありますが…今回の環境では既に node (-v 4.1) が入ってしまっており、依存関係を確認できていません。

余談その2:
ここで、Rackとは? という疑問が発生しますが、Webサーバーですね。私の理解が正しければ、他言語と横並びで見た時の分類は以下のとおり。

Lang Web Server I/F仕様 Webサーバーの実装(一例)
Ruby Rack protocol Rack
Python WSGI *1
Perl PSGI Plack

"Webサーバーの実装" 列には、I/F仕様の策定と同時に行われた実装という意味で記載しています。無論、各I/F仕様に対応したフレームワークは無数にあります。(*1)部分については歴史を熟知していませんが、PythonはI/F仕様の策定のみ中心に行われ、実装については周辺のフレームワークに任せたという理解でよいのだろうか??

話を戻します。

$ gem install bundler
$ gem install rails
    or
  gem install rails --version="~> 4.2.3"

rails のバージョンはできるだけ Redmine に合わせておきたいと思いつつバージョン指定無しでインストールしてしまいましたが、2016/05/17時点でインストールされるバージョンは 4.2.6 だったためセーフです。

バージョンを指定したい場合は最終行のように指定するようです。

$ rbenv rehash
$ rails ­-v
Rails 4.2.6
$ bundle -v
Bundler version 1.12.3

それぞれインストールが完了し、バージョンが表示されることを確認します。

ここまできたら、Railsが期待したとおりに実行できるかを確認します。

# 作業ディレクトリ。どこでもよい
$ cd ~/tmp

# 1個ずつ段取りを確認するために、最初は bundler でのモジュール一括インストールをスキップする
$ rails new foo --skip-bundle

# その後手動で bundle をインストールする
# --path を付与すべきか正確に確認していないが、 vendor/bundle に
# インストールするケースが一般的なのは認識があるので名に付与しておく
$ bundle install
   or
 bundle install --path vendor/bundle # <--こっちがいい?

続いて、Rails登場初期によくデモ映像で見かけた scaffold でテスト。

# ユーザー管理機能の作成
$ rails g scaffold user name:string email:string
$ rake db:migrate
$ rails s

# 上手く起動したように見えたら、ページを開く
open http://localhost:3000/users 

Rails のサンプルサイトが表示されたら、ここまでは正常完了です。

Redmineを起動してみる

bundle install と エラー

$ cd ~/tmp

# redmine を入手します
$ git clone https://github.com/redmine/redmine.git

$ cd ./redmine
$ bundle install --path vendor/bundle

すると、 rmagic のインストールに失敗し Failed となってしまいました。

$ ~/tmp/redmine ❯❯❯ gem install rmagick -v '2.15.4'
Building native extensions.  This could take a while...
ERROR:  Error installing rmagick:
        ERROR: Failed to build gem native extension.

    /Users/me/.rbenv/versions/2.2.4/bin/ruby -r ./siteconf20160516-94505-17jpn7a.rb extconf.rb
checking for clang... yes
checking for Magick-config... no
checking for pkg-config... yes
Package MagickCore was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickCore.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickCore' found
checking for outdated ImageMagick version (<= 6.4.9)... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/Users/me/.rbenv/versions/2.2.4/bin/$(RUBY_BASE_NAME)

extconf failed, exit code 1

Gem files will remain installed in /Users/me/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/rmagick-2.15.4 for inspection.
Results logged to /Users/me/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/rmagick-2.15.4/gem_make.out

いくつか調べてみて、PKG_CONFIG_PATH の記載がないからなどの情報がありましたが、ImageMagic をインストールしていないという報告もあり。確かに本環境ではインストールしていませんということで、brew を用いてインストールします。

# update していない場合は事前にやっておきましょう
$ brew update

$ brew install imagemagick

ImageMagickをインストールすると、以下コマンドが通過するようになりました。

$ gem install rmagick -v '2.15.4'

Building native extensions.  This could take a while...
Successfully installed rmagick-2.15.4
1 gem installed

database の設定

今回の環境はただの Plugin 開発ですので、シンプルにSQLITEの環境で凌ぎます。事前にコンフィグ指定をしておかないと以下に示す Gemfile の部分で警告が出てしまい、generate も行えませんでした。

警告の発生元:

database_file = File.join(File.dirname(__FILE__), "config/database.yml")
if File.exist?(database_file)
  database_config = YAML::load(ERB.new(IO.read(database_file)).result)
  adapters = database_config.values.map {|c| c['adapter']}.compact.uniq
  if adapters.any?
    adapters.each do |adapter|
      case adapter
      when 'mysql2'
        gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw, :x64_mingw]
        gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
      when 'mysql'
        gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
      when /postgresql/
        gem "pg", "~> 0.18.1", :platforms => [:mri, :mingw, :x64_mingw]
        gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
      when /sqlite3/
        gem "sqlite3", :platforms => [:mri, :mingw, :x64_mingw]
        gem "jdbc-sqlite3", ">= 3.8.10.1", :platforms => :jruby
        gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
      when /sqlserver/
        gem "tiny_tds", "~> 0.6.2", :platforms => [:mri, :mingw, :x64_mingw]
        gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw, :x64_mingw]
      else
        warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems")
      end
    end
  else
    warn("No adapter found in config/database.yml, please configure it first")
  end
else
  warn("Please configure your config/database.yml first")
end

database の設定:

$ cp config/database.yml.example config/database.yml
$ vi config/database.yml

~~~ ↓ここから #sqliteファイル名は適当に設定しています
production:
  adapter: sqlite3
  database: db/redmine_production.sqlite3

development:
  adapter: sqlite3
  database: db/redmine_development.sqlite3

test:
  adapter: sqlite3
  database: db/redmine_test.sqlite3

DBの作成(マイグレーション):

$ bundle exec rake db:create
$ bundle exec rake db:migrate

Redmine (Rack) を起動してみる

そろそろ終わりかとおもいきや、 secret key の作成が必要とのこと。Rails 4.1 からのセキュリティ関連の制約のようです。

$ rails s

warning: duplicated key at line 466 ignored: "inodot"
=> Booting WEBrick
=> Rails 4.2.6 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2016-05-16 23:39:41] INFO  WEBrick 1.3.1
[2016-05-16 23:39:41] INFO  ruby 2.2.4 (2015-12-16) [x86_64-darwin15]
[2016-05-16 23:39:41] INFO  WEBrick::HTTPServer#start: pid=43973 port=3000
DEPRECATION WARNING: You didn't set `secret_key_base`. Read the upgrade documentation to learn more about this new config option. (called from service at /Users/me/.rbenv/versions/2.2.4/lib/ruby/2.2.0/webrick/httpserver.rb:138)
DEPRECATION WARNING: You didn't set `secret_key_base`. Read the upgrade documentation to learn more about this new config option. (called from service at /Users/me/.rbenv/versions/2.2.4/lib/ruby/2.2.0/webrick/httpserver.rb:138)
[2016-05-16 23:39:48] ERROR RuntimeError: Missing `secret_token` and `secret_key_base` for 'development' environment, set these values in `config/secrets.yml`

なので、以下のとおり secret key を生成のうえ、config/secrets.yml に設定を行います。

# secret key の生成。pbcopy は MacOSのクリップボードにコピーする
# コマンドなので、非Mac環境の場合は生テキストをコピ&ペーストします
$ bundle exec rake secret RAILS_ENV=development | pbcopy

vi config/secrets.yml

~~~ ↓ここから
development:
  secret_key_base: 83f2xxxxxxxxxxx <-ここに記載

そして起動成功へ

$ rails s

/Users/me/tmp/redmine/vendor/bundle/ruby/2.2.0/gems/htmlentities-4.3.1/lib/htmlentities/mappings/expanded.rb:465: warning: duplicated key at line 466 ignored: "inodot"
=> Booting WEBrick
=> Rails 4.2.6 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2016-05-17 00:49:42] INFO  WEBrick 1.3.1
[2016-05-17 00:49:42] INFO  ruby 2.2.4 (2015-12-16) [x86_64-darwin15]
[2016-05-17 00:49:42] INFO  WEBrick::HTTPServer#start: pid=xxxx port=3000

htmlentities モジュールにて何やら警告が発生していますが、直近の開発には問題なさそうなので無視します。

Redmine Plugin の作成

今回は Plugin の generate ができることまで確認して終了。

$ rails g redmine_plugin redmine_your_plugin_name

# redmine_your_plugin_name: 作成したいプラグイン名を記載

Rails & Redmine 環境構築のまとめ

Rails & Redmine 環境を El Capitan に構築しました。その手順は以下のとおりです。

  1. Rails サポート対象の Ruby 実行環境の準備
  2. rbenv (←とにかく期待するバージョンの ruby があればいいので必須ではない)
  3. brew のインストール
  4. gem モジュールのインストール
    • gem install powder
    • gem install bundler
    • gem install rails
  5. ImageMagick のインストール (brew install imagemagick)
  6. Rails実行環境の設定
    • config/database.yml の設定
    • config/secrets.yml の設定

最初は普段運用している Docker の Redmineコンテナを用いればよいかなとも思っていたのですが、docker-compose コマンドに依存しすぎると中ではどのような構成でどう動いているのか理解が弱くなってしまうため、あえて新規構築を行いました。

今回の作業を経て、bundle がどのタイミングで何をしてくれるだとか、どのタイミングでモジュールをインストールするのかといったことの理解が深まったように思います。

本題のPlugin開発着手については、またそのうち。

参考資料

Let's Encrypt 証明書をサーバー無停止で新規発行する(Nginx編)

はじめに - 本記事のゴール

先日、WordPress環境向けに Let's Encrypt 証明書の適用を試みました。

そこでは Standalone プラグイン(現在稼働中のHTTPサービスを一度停止して証明書を発行する方法)を主に試しましたが、ここでは Webroot プラグイン(現在稼働中のHTTPサービスは継続したまま新規に発行する)方法を試みます。

前回の記事:

blog.exlair.net

Let's Encrypt 証明書作成の準備をする (Nginx環境)

前回記事でも "webrootの仕組み" として少し記載しました。

/path/to/doc_root/ で設定した部分に、スクリプトが /.well-known/acme-challenge/xxxxxx(←ランダム文字) という構造でファイルを作成し認証しているようです。従って、WordPressをリバースプロキシしているnginxに対しては、以下のコンフィグを適用する必要があります。

ですので、Let's Encrypt スクリプトがアクセスするためのパスを以下の通り定義します。

    server_name exlair.hatenablog.com;

    ...(中略…各自のコンフィグ)...

    location ^~ /.well-known/acme-challenge {
        root /path/to/doc_root/;
    }
  • 新規発行したいドメインの VirtualHost 設定に適用します。ここでは、exlair.hatenablog.com に適用するイメージを記載しています。
  • /path/to/doc/root/ 部分は何でも構いません。用途が限定的なので、現在稼働しているサービスに依存しない場所へ新規作成したほうが運用しやすいと個人的には思います。別の新規ドメイン作成時にも同じものが流用できるので。
  • パーミッションは、Webページが表示できる最低限のレベルで問題ありません(スクリプト実行時には結局、rootでランダム文字のディレクトリが生成されるため)。

Let's Encrypt 証明書を作成する

証明書の作成コマンドを実行する

  • 作成したいドメイン名「YOUR_DOMAIN」部分に置き換えて記載してください
  • /path/to/doc_root 部分は、Let's Encrypt 証明書作成の準備をする で設定したスクリプト用の公開パスです
$ ./letsencrypt-auto certonly --webroot -w /path/to/doc_root -d YOUR_DOMAIN

Checking for new version...
Requesting root privileges to run letsencrypt...
[sudo] password for username_xxxxx:

認証実行時の Let's Encrypt サーバーからのアクセス(1リクエスト)

66.133.109.36 "GET /.well-known/acme-challenge/xxYYzzLSkLeS4sSin_KLhyyyyyyyyyyo7xxxxxxxxxx HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"

なお、今回実行時のIPアドレスは上記のとおりでしたが、Let's Encrypt 総合ポータル よくある質問にある通りIPアドレスのリストは随時変更される可能性があるため公開されていません。保証できませんということですね。

Let's Encrypt が認証時に使用するIPアドレスは何ですか?

Let's Encrypt では、サーバの認証(ドメイン・サブドメイン使用権者の認証のためのWebサーバへの接続)に使用するIPアドレスのリストを公開していません。これは、使用されるIPアドレスが随時変更される可能性があるためです。 また、将来的には、一回の認証プロセスにおいて、複数のIPアドレスから接続するようになるかもしれません。

証明書作結果(成功)

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem. Your cert will
   expire on 2016-08-06. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
  • YOUR_DOMAIN 部分は、-d オプションで指定したドメインが記載されます。
  • expire 部分には、証明書の有効期間=発行日から3ヶ月後の月日が記載されます。

証明書の作成ディレクトリを確認してみます。

$ sudo ls -al /etc/letsencrypt/live/

drwx------ 4 root root 4096 May  8 15:30 .
drwxr-xr-x 8 root root 4096 Apr 13 22:31 ..
drwxr-xr-x 2 root root 4096 Apr 17 15:56 DOMAIN1     (←以前作成したもの)
drwxr-xr-x 2 root root 4096 May  8 15:30 YOUR_DOMAIN (←今回作成したもの)

無事作成されました。

自動更新の設定

Let's Encrypt 証明書の有効期間は発行日から3ヶ月後で短いため、cron による自動更新が推奨されています。

letsencrypt-auto renew コマンドを実行するだけで、新規発行時に作成されたコンフィグを用いてうまいこと更新してくれるようで、既にスクリプトも仕込んであるのですが、まだ更新時期に突入したことがないので成否の確証がとれていません…。

ですので結果はおって更新したいと思います。

まとめ

以上、サーバーのダウンタイム無しで特定 VirtualHost 向けの証明書を作成する手順でした。

なお、今回 Let's Encrypt スクリプト群を git pull したところ、わずか数週間でいろいろファイル郡が更新されていました。nginx 向けの更新簡易化スクリプトがもしかしたら安定利用できる状態になっているかもしれませんが、その辺の最新状況をウォッチできていないため、本記事では汎用的な方法で行いました。

(最初に Let's Encrypt を試した時は、Nginx向けの簡易化プラグイン?は非推奨になっていました)

シンプルな Docker 実行環境 CentOS 7.2 に構築する

本記事の目標

Docker実行環境を CentOS で整えるにあたり、2016/4/23 現在での情報をまとめるものです。

f:id:exlair:20160423224522p:plain

OSのバージョンは、CentOS Linux release 7.2.1511 (Core) を利用しています。

背景(余談)

以前、CentOS6.6 から CentOS 7.0 へアップグレードするにあたり、色々と依存関係にひっかかっかりつつ強引なアップグレードを強行したのですが、その弊害でいま新たなパッケージをインストールすると失敗するケースが度々発生してしまうおかしい状態になっていました。

当該マシンでは Docker 1.2 を利用してましたが、現在はAPIバージョンも変更されており新たな Docker Image の取得さえ困難に…ということで、新規インストールすることとしました。

前提:マシンの準備

ESXiで作成した仮想マシン上にインストールすることを想定しています。 もちろん、物理マシンでも手順は大きく変わりませんが、余計な作業をしています。

OS イメージの入手

Download CentOS

http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-1511.iso

OSインストール直後の設定

パッケージを最新化

とりあえず何も考えずに各パッケージを最新化します。

yum update

仮想マシン環境下におけるポイント

仮想マシンを作成する際に NIC を VMXNET3 でしか作成しなかった場合、VMwareTooolsをインストールしていない状態ではIPアドレスの割当が行われず、ネットワークへの接続ができなくなります。そして、VMwareTools を利用しようにも Minimal 環境下では Perl もインストールされていませんので詰んでしまいます。

その場合、一度シャットダウンした後、仮想マシンの編集で NIC を E1000 で追加して再度起動すると、DHCPでアドレスが取得されるはずです。

VMwareTools もどきの open-vm-tools をインストール

レポジトリを追加する

vi /etc/yum.repos.d/vmware-tools.repo

# 記載内容は以下のとおり 
[vmware-tools]
name = VMware Tools
baseurl = http://packages.vmware.com/packages/rhel7/x86_64/
enabled = 1
gpgcheck = 1

open-vm-tools をインストールする。

# 鍵の取得
rpm --import http://packages.vmware.com/tools/keys/VMWARE-PACKAGING-GPG-RSA-KEY.pub

# アップデートしたうえで open-vm-tools が見つかるか確認
yum update && yum search open-vm-tools

# 問題なければインストール
yum install open-vm-tools

インストール前の ip a show (ifconfig相当)

inet6など若干省略しています。ens160(VMXNET3で作成したNIC)にIPアドレスが割り振られていないことが分かります。なお、数字は環境により異なります。

[root@yourhost ~]# ip a show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
3: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:XX:XX:XX:YY brd ff:ff:ff:ff:ff:ff
    inet 192.168.4.56/24 brd 10.0.4.255 scope global dynamic ens33
       valid_lft 85504sec preferred_lft 85504sec

ネットワーク設定の変更

先ほど割り当てたNICは暫定的なものなので、VMXNET3 のNICに対し固定IPを設定します。(もちろん、DHCPで運用を続ける場合は必要ありません)

従来 /etc/sysconfig/network-scriptsifcfg-xxxx を直接変更してきましたが、CentOS 7 からは nmcli コマンドが推奨されているようなので、コンフィグを直接編集する代わりにコマンドを利用してみます。

CentOS 7.1.1503ではnmcliでIPアドレスを固定する方法が変更された - Qiita

# nmcli connection modify ens160 ipv4.method manual ipv4.addresses 10.0.4.13/24 ipv4.gateway 10.0.4.1 ipv4.dns "10.0.4.12 10.0.4.15"

すると、/etc/sysconfig/network-scripts でも変更した対象のコンフィグが変更されます。その後rebootにて固定IPが設定されていることを確認しましょう。

# systemctl restart NetworkManager

こちらのコマンドでもよいみたいですが。 なお、ONBOOT が yes になっているかしっかり確認しましょう。

- ONBOOT=no
+ ONBOOT=yes

OSインストール後の定番の設定

各自好みもでてきますので、必要な設定を行ってください。 ここでは以下の状態を目指します。

  • docker コマンドは一般ユーザーで行えるようにする (<--Dockerインストール後の作業)
  • root には原則スイッチしない。sudoで行う
  • SSHは鍵認証のみに制限し、かつログイン可能ユーザーも限定する
  • SELinuxはOFFに…せず、本当に面倒くさくなった時にOFFにする
    • 最初っからセキュリティレベルを下げすぎる意味はないという意味
    • Dockerでの利用が主ならば、それで済むことも多いはず
    • 参考記事:SELinux と Docker と OpenShift v3 - Qiita
  • シェルはzsh。コンフィグには Prezto を利用して楽をする

その他インストールパッケージ群

  • sudo yum install -y git
  • sudo yum install -y zsh

OSバージョンアップなど今後の運用負担を考慮し、できるだけ最小限にしています。

各種リンク

Docker のインストール

$ yum info docker

$ sudo yum -y install docker
$ sudo systemctl status docker

インストール直後は起動していません。

● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: http://docs.docker.com

念のため再起動して、自動起動するか確認します。

● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2016-04-23 21:43:01 JST; 15s ago
     Docs: http://docs.docker.com
 Main PID: 891 (sh)
   CGroup: /system.slice/docker.service
           ├─891 /bin/sh -c /usr/bin/docker daemon $OPTIONS            $DOCKER_STORAGE_OPTIONS            $DOCKER_NETWORK_OPTIONS    ...
           ├─894 /usr/bin/docker daemon --selinux-enabled
           └─895 /usr/bin/forward-journald -tag docker

上がってこない場合は、有効化しましょう。

$ sudo systemctl enable docker

一般ユーザーで Docker コマンドを利用する設定

利用したいユーザーを Docker のグループに追加します。 CentOS 7 の場合、グループは dockerroot のようですね。docker とやると、対象は存在しないと警告されてしまいます。

で、グループ追加するだけでOK...のはずだったのですが。

$ sudo gpasswd -a user_onamae dockerroot
$ docker ps
Cannot connect to the Docker daemon. Is the docker daemon running on this host?

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED   ...

ここでは、sudo にて docker ps コマンド(動作確認を目的として、稼働中のコンテナを表示するコマンド)を叩くときちんと動作していますが、通常ユーザーでは警告が表示され上手くいかないという状態を表しています。

とりあえず対処策として、下記対処で様子を見ることとしました。

# Modify these options if you want to change the way the docker daemon runs
#OPTIONS='--selinux-enabled'
OPTIONS='--selinux-enabled -G dockerroot'

via Ec2 Centos7 + Docker 1.6 · Issue #13098 · docker/docker

修正前の動作 docker.sock の状態

$ ls -l /var/run/ | grep docker
srw-rw----.  1 root root    0  4月 23 21:49 docker.sock

修正後の動作 docker.sock の状態

$ ls -l /var/run/ | grep docker
srw-rw----.  1 root dockerroot    0  4月 23 21:57 docker.sock

動作確認:

$ docker pull busybox

Using default tag: latest
Trying to pull repository docker.io/library/busybox ... latest: Pulling from library/busybox
4b51ded9aed1: Pull complete 
307ac631f1b5: Pull complete 
Digest: sha256:4a887a2326ec9e0fa90cce7b4764b0e627b5d6afcb81a3f73c85dc29cea00048
Status: Downloaded newer image for docker.io/busybox:latest


$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker.io/busybox   latest              307ac631f1b5        5 weeks ago         1.113 MB

$ docker run -it --rm busybox
Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning.
/ # 
/ # exit

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

ひとまず動いているようです。 ここでは、

  1. 軽量のイメージ busybox をダウンロード-
  2. その後 busybox Image から docker run にてコンテナを起動(終了したらコンテナを即削除する --rm オプション付)
  3. exit にてコンテナを終了
  4. docker ps -a (終了しているコンテナを含め全てを表示) し、何も表示されない=コンテナが削除されていること

を一連のステップで確認しています。

これで Docker の実行環境が整いました。

$ docker version
Client:
 Version:         1.9.1
 API version:     1.21
 Package version: docker-1.9.1-25.el7.centos.x86_64
 Go version:      go1.4.2
 Git commit:      78ee77d/1.9.1
 Built:           
 OS/Arch:         linux/amd64

Server:
 Version:         1.9.1
 API version:     1.21
 Package version: docker-1.9.1-25.el7.centos.x86_64
 Go version:      go1.4.2
 Git commit:      78ee77d/1.9.1
 Built:           
 OS/Arch:         linux/amd64

ここまでで消費しているディスクサイズは 1.5GB ほどで、とてもスッキリしています。

$ df -h
ファイルシス               サイズ  使用  残り   使用%  マウント位置
/dev/mapper/centos-root    48G  1.3G   47G    3%  /
tmpfs                     921M  8.5M  912M    1%  /run
/dev/sda1                 497M  166M  332M   34%  /boot
 ..(一部省略)

なお、ここでは触れていませんが Docker Compose のインストールもほぼ必須です。ないことで何かができなくなるということはありませんが、Dockerコンテナの扱いが非常に楽になります。Dockerで実行可能な各種ソフトウェア群の Quick Start なんかでは Docker Compose のコンフィグがベタ書きされていることも多くあります。

Install Compose

まとめ

Docker を使うための最低限のシンプルな環境構築ということで一通りの手順を記載しました。

この頃、OSSなど多くのものが Docker Image を配布してくれているおかげで、「ちょっと試してみたいな」と思った時の敷居が非常に小さくなり嬉しいです。 とりあえず docker pull && docker run すればいいのですから。

WordPressを無料のSSL証明書 Let's Encrypt でHTTPS化する

無料のSSL証明書 Let's Encrypt とは

WebサイトのHTTPS化が加速しているなか、個人利用においては証明書の取得コストが若干の障壁になっていましたが、無料のSSL証明書発行の仕組み Lets's Encrypt が正式版になったので利用してみます。

Let's Encrypt - Free SSL/TLS Certificates

SSLを用いたHTTP通信の暗号化を誰でも手軽に行えるようにするために立ち上がったのがEFF、MozillaCisco SystemsAkamai Technologies、IdenTrust、ミシガン大学の研究者などで、これらのメンバーがHTTPS普及のためにスタートした取り組みが「Let's Encrypt」です。これまで手間がかかり金銭的な負担も大きいと言われてきたサーバー証明書の発行を無料で行えるようになるのですが、同取り組みはついにベータ版から正式版にサービスを移行しています。

無料で証明書を発行してHTTPSの導入をサポートする「Let’s Encrypt」がベータ版から正式版に - GIGAZINE

本エントリの目的

  • (#1) 手始めに、WordPressで構築しているBLOGをHTTPS化します
  • (#2) 当該サイトは、R-PROXY(nginx)による多段構成でWordPressに接続します

構成は以下のとおりとします。

+--------------+            +----------+            +--------------+
| Client(User) | ---------> | R-Proxy  | ---------> | Apache       |
|              | HTTPS(#1)  | (nginx)  |  HTTP(#2)  | (wordpress)  |
+--------------+  http2     +----------+            +--------------+

ハマりポイント1. WordPressがHTTPのURLを吐きつづける

(#1)のR-PROXY(nginx)に対してSSL証明書を適用する部分は証明書が Let's Encrypt 発行になるだけで、適用プロセスそのものは通常の証明書と同様、特別なことはありません。むしろCSR発行が作業不要なので楽です。しかし(#2)の部分でHTTPを利用しているところが曲者で、WordPress本体としてはHTTPでアクセスを受け付けているのでHTMLに書き出される各種リソースURLの部分がHTTPのままになってしまい、ユーザーアクセスがいつまでたってもHTTPSに置き換わりません。

対処として以下のとおりコンフィグレーションします。

バックエンドのWordPressに対してプロトコルを通知する

httpsであることを通知するために X-Forwarded-Proto ヘッダがよく利用されています。nginxでもそれを連携できるようします。

前提として、リバースプロキシ構成を取る時の一般的なコンフィグ location / { ... } にてバックエンドのWordPressに中継している場合とします。

    location / {
        proxy_pass http://wordpress;
        ...中略
        proxy_set_header X-Forwarded-Proto $scheme;
    }

WordPress側でのHTTPS出力設定

wp-config.php に以下の記載を追加します。

  • WP_HOME および WP_SITEURL でのアドレスは各自ご利用のものに書き換えます。HTTPS化を前提としていますので、もちろん https://xxxx で記載します
  • FORCE_SSL_LOGINFORCE_SSL_ADMIN は、管理画面のみをSSL化する場合のオプションです。サイト全体をSSL化する場合は必要ありませんが明示のために気分で付与しています
  • if文で HTTP_X_FORWARDED_PROTO に関する記載があります。ここで、WORDPRESSがコンテンツ出力する時のURL群に適用する scheme を動的に変化させています。つまり、ユーザーからHTTPSアクセスが発生した祭には nginx にて X-Forwarded-Proto: httpsWordPressのサーバーへ連携され、WordPressではそれを見てBODY出力の schemeHTTPSにします
define('WP_HOME',    'https://example.com');
define('WP_SITEURL', 'https://example.com');
define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_ADMIN', true);
if ( ! empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) {
  $_SERVER['HTTPS']='on';
}

HTTP_X_FORWARDED_PROTO の設定があれば、わざわざ WP_HOME, WP_SITEURL に対して設定は必要ないようにも見えるのですが、http のままにしておくと以下のように この接続は安全ではありません。このページの一部(画像など)は安全ではありません。 と警告が発生します。

f:id:exlair:20160417181300p:plain

f:id:exlair:20160417181305p:plain

なお、WP_HOME, WP_SITEURLWordPress 管理者画面 -> 設定 -> 一般設定にある項目と同じ役割を示しています。

wp-config.php上の表現 管理者画面上の表現
WP_SITEURL WordPress アドレス (URL)
WP_HOME サイトアドレス (URL)

f:id:exlair:20160417181309p:plain

WP_SITEURL = サイトアドレス(URL)のように見えますが入れ子になっているので要注意

管理者画面上から変更してもよいのですが、環境によっては設定を失敗すると無限ループが発生して管理者画面へアクセスできなくなり、その場合はSQLを直接叩いて修正する必要がでてきます。wp-config.phpの方が効力が強いので上書き可能ですが、構成検証のし易さの観点で、ここでは初めから wp-config.php に記載しておきます。

無限ループが発生してしまった場合

wp_optionsテーブルに対して、以下のSQLで対処可能です。 現在利用しているバージョンのWordPressでも同様であるかは念のための確認を推奨します。

各キーが存在するか、そしてその内容が現在の遭遇している状態と正しいかを確認する。

select * from wp_options where option_name = 'home';
select * from wp_options where option_name = 'siteurl';

値を書き換える。

update wp_options set option_value = '(正しいURL)' where option_id = (確認したoption_id);

ハマりポイント2. 一部URLがHTTPのまま残り続ける

恐らく理由としては2通りです。

  1. plugin に http 固定値での文字列が埋め込まれている
  2. 自分で作成したwidgetやテーマなど何らかの形でhttpの固定値が埋め込まれている

この状態だと、いつまで経ってもブラウザのHTTPS暗号化マーク(鍵マーク)が表示されません。これは、対象のページ内で利用されているコンテンツが全てHTTPSで保護された接続になっている必要があるためです。

地道ですが、Chromeデベロッパーツール(Google 検索)などを利用しながらHTTPアクセスとなっている部分を確認し、スクリプトの修正が、場合によってはPluginの見直し(廃止やその他同類プラグインへの置き換え)を実施します。

Let's Encrypt 証明書適用方法

ハマりポイントを先に記載したところで、以下通常の適用方法を記載します。 以下の点を予め抑えておくと理解が捗ります。

  • CSR作成や証明書発行機関への送付などの面倒臭い処理は、すべてLet's Encryptが配布しているスクリプトにより自動化されている
  • 証明書などの各種ファイルは、/etc/letsencrypt/ドメイン毎に格納されている
  • 発行された証明書の有効期限は90日と短め。自動更新のcronを設定することが望ましい
  • 発行時にはDV認証(ドメイン保有者であることの確認。組織の認証は行わない)が行われる。その際、スクリプト実行時のサーバーの80または443が利用される
  • 更新(renew)コマンドは、証明書有効期限30日未満の場合は実行されない

80または443をどういう優先順で利用されるのかは調べておらず、把握していません。 DV認証時、既に動いているWebサーバーがある場合はポートが競合しますので、対応として2種類の方法が存在します。

共通作業:Let's Encryptが配布するスクリプトをダウンロード

gitを利用します。保存場所は任意で構いません。

$ git clone https://github.com/letsencrypt/letsencrypt
$ cd letsencrypt
$ ./letsencrypt-auto --help

実行時にsudoをする必要はありません。必要な時はスクリプト内でパスワードを求められます。

稼働中のサーバーを落として認証用のサーバーを起動 (standalone)

sandaloneモードでスクリプトを起動します。

$ ./letsencrypt-auto certonly -a standalone -d example.com
                                               (ドメイン部分は適宜修正)

メッセージと共に /etc/letsencrypt/live/(ドメイン)/fullchain.pem が生成されていれば成功です。

DV認証そのものにあたっては既存Webサーバーに対して設定変更が不要ですが、cronでの更新自動化を行うには、nginxの停止・起動を含めて処理を組む必要があります。

現在稼働中サーバーに認証用のアクセスを許容する(webroot)

$ ./letsencrypt-auto certonly --webroot -w /path/to/doc_root/ -d example.com
                                                       (ドメイン部分は適宜修正)

webrootの仕組み
/path/to/doc_root/で設定した部分に、スクリプト/.well-known/acme-challenge/xxxxxx(←ランダム文字) という構造でファイルを作成し認証しているようです。従って、WordPressをリバースプロキシしているnginxに対しては、以下のコンフィグを適用する必要があります。

    location ^~ /.well-known/acme-challenge {
        root /path/to/doc_root/;
    }

standaloneモードに対し、こちらの場合は一度設定してしまえばnginxの停止・起動は不要になります。但し、SSL証明書を更新後の再読み込みにはreloadが必要ですので、その処理は残ります。

nginxへのSSL証明書適用

ここは一般的なノウハウとなります。証明書は以下のとおり。

    ssl_certificate /etc/letsencrypt/live/(ドメイン)/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/(ドメイン)/privkey.pem;

しばらくは 80, 443 いずれも残した状態で動作確認を行い、443ポートでの展開ができる状態になったら、元々のHTTP(80ポート)へのアクセスは全てHTTPSページで誘導するよう、リダイレクト設定を行います。

server {
    listen 80; # httpへのアクセスをhttpsへリダイレクトする
    listen [::]:80;
    return 301 https://$host$request_uri; 
}

server {
    listen 443 ssl;
    ...
    省略
}

まとめ

証明書発行は楽だし、更新も自動化できるし、ドメイン数を気にせず発行できるし、良い仕組みだと思います。

PHP-FPM インストール時に UnicodeDecodeError 発生(Ubuntu 14.04)

PHP-FPM をインストールする

Docker上でPHP-FPM用を構築しようとすると下記コマンドでインストールを試みた。

apt-get install -y php5-fpm

しかし、下記の通り依存関係ではまってしまった。

php5-common (= 5.5.9+dfsg-1ubuntu4.11) but 5.5.9+dfsg-1ubuntu4.12 is to be installed

手っ取り早く解決したかったので、外部レポジトリを追加。

add-apt-repository -y ppa:ondrej/php5

※ もし add-apt-repository が使えない場合は先にこちらを投入。

apt-get install -y software-properties-common

UnicodeDecodeError: 'ascii' codec can't decode byte

ここで一応使えるようになったのだが、レポジトリ追加時にエンコードのエラーが発生した。

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 92: ordinal not in range(128)

海外のフォーラム等で調べたところ locale の問題ので、下記方法で解決する。

locale-gen en_US.UTF-8
LC_ALL=en_US.UTF-8 add-apt-repository -y ppa:ondrej/php5

閲覧中ページをbitlyで短縮URLにしてからTwitterクライアントの投稿欄に入力するブックマークレット

閲覧中ページをbitlyで短縮URLにしてからTwitterクライアントの投稿欄に入力するブックマークレット

これなに?

閲覧中サイトのURLを短縮URL化したうえで、Twitterクライアント(公式)のTweet投稿画面を開いてページタイトルと短縮URLを入力するものです。

例えば本ページの場合はこんな感じになります。
『 || 閲覧中ページをbitlyで短縮URLにしてからTwitterクライアントの投稿欄に入力するブックマークレット - まとめノート (exlair's note) http://bit.ly/1cxACM7

Mac / iOS(8.xで確認) いずれも Safari / Chrome で動作することを確認しています。

きっかけ

ブラウザで閲覧中のサイトをTweetして共有したいと思った時、

  • URLをはりつけるのが面倒臭い
  • ページタイトルも表示したいんだけどなぁ
  • 文字数の節約/クリック数統計のためにbit.lyを使いたい
  • iOS標準の共有機能によるTweetはいまいち好きになれない

といったシーンがあるかと思います。また、「家ではMacだけど外ではiPhone/iPadなんだよね」といったことも最近ではよくある話。そして、利用しているデバイスに応じて方法が変わるのも面倒くさい話。

というわけで、閲覧中のページを共有したい時 MacでもiPhoneでも、Twitter公式クライアントを入力窓にすればいいや、というのが今回の話。

使い方

普通のブックマークレットです。
bit.lyで短縮URLを作成するために自身のIDとAPIkeyを利用する前提で作っていますので、適宜書き換えてください。 それぞれ、i='your user name'(メールアドレスではないもの)とk='your bitly API key'(R_ から始まる桁数の多い文字列)の部分です。

Tweet w/t Bitly
(Mac向けのリンク。お気に入り欄にドラッグして登録してください)

Tweet w/t Bitly
(iOS向けリンク。iOSの場合はブックマークレットを直接お気に入り登録できないため、この仮URLをお気に入り登録したうえで、その後URLを下記のコードに書き換えてください。全部上書きでOKです)

なお、Safari も Chrome も、MacとiOS間でブックマークを共有(割と早い)する機能がありますので、その場合はブラウザで登録した方が楽です。

javascript:(function(d,l){ var i,k,u,s;i='your user name';k='your bitly API key';u='http://api.bit.ly/v3/shorten?format=json&callback=bitlycb&login='+i+'&apiKey='+k+'&longUrl='+encodeURIComponent(l.href);s=d.createElement('script');s.type='text/javascript';s.src=u;d.body.appendChild(s);})(document,location);var bitlycb=function(r){ var t,h;t=document.title;t=t?t:'';h='twitter://post?message='+encodeURIComponent(' || '+t+' '+r.data.url);console.log(h);location.href=h;};

他のTwitterクライアントを使いたい場合

ひとつのコードでMacもiOSも対応できる理由は、Twitterクライアントを起動するためのURIスキームが twitter:// となっており同一であるためです。ですので、以下の対応が可能です。

  • 他のTwitterクライアントでもMacとiOS対応しているものがあり、かつクライアントを呼び出すURLスキームの部分が同一である場合
  • マルチデバイス対応なんていらない(例えばiPhoneだけでいいよ)という場合は、選択肢がグッと広がります。いくつかサンプルを記載しますが、色々ありますので調べてください。

なお、これはTwitterクライアントに限定されるものではありません。

実装

(function(d,l){
    var i,k,u,s;
    i='your user name';
    k='your bitly API key';
    u='http://api.bit.ly/v3/shorten?format=json&callback=bitlycb&login='+i+'&apiKey='+k+'&longUrl='+encodeURIComponent(l.href);
    s=d.createElement('script');
    s.type='text/javascript';
    s.src=u;
    d.body.appendChild(s);
})(document,location);

var bitlycb = function(r){
    var t,h;
    t=document.title;
    t=t?t:'';
    h='twitter://post?message='+encodeURIComponent(' || '+t+' '+r.data.url);
    console.log(h);
    location.href=h;
};

CentOS7でsambaが起動しない

CentOS7の環境でsmbを起動するためにsystemctl start smbしたところ…

12月 06 15:27:12 cent sudo[21191]: username : TTY=pts/0 ; PWD=/etc ; USER=root ; COMMAND=/bin/systemctl start smb.service
12月 06 15:27:12 cent systemd[1]: Starting Samba SMB Daemon...
-- Subject: Unit smb.service has begun with start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit smb.service has begun starting up.
12月 06 15:27:12 cent systemd[1]: smb.service: main process exited, code=exited, status=1/FAILURE
12月 06 15:27:12 cent systemd[1]: Failed to start Samba SMB Daemon.
-- Subject: Unit smb.service has failed
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit smb.service has failed.
-- 
-- The result is failed.
12月 06 15:27:12 cent systemd[1]: Unit smb.service entered failed state.

チェックポイント

  1. systemdのstatusがenableになっているか? systemctl list-unit-files
  2. /etc/samba/smb.conf は正しく設定されているか?

CentOS6.xから7へアップグレードした際に調子が悪くなったので、yum remove sambaして再インストール。他にもいろいろなエラーが発生したので、プロセス管理機構が大幅に変わるようなアップデートは新規インストールした方がよいなと改めて思う。

OSXからCentOS6.5へknife soloしたい!発生したエラーと対策

随分前に少し使って放置していたChef実行環境(knife solo)を改めて使ってみる。

  • Client環境 (knife solo): OSX
  • Server環境: CentOS 6.5: インストールしてアップデートした程度の状況。ほぼ初期状態

まず最低限ということで、

  1. 接続先情報を初期化する knife solo prepare の実行完了
  2. recipeを実行する knife solo cook の実行完了

を最初の目標にする。recipeはユーザを作成する程度の簡単なものにした。

Chef & knife 関連はさすがに環境変化が激しく、Web検索で見つかる情報はどれも古く感じてしまうのが辛いところ。いろいろな情報をかき集めながら初期環境を構築した。正常パターンは追々メモすることとして、本記事ではハマったところのエラーを記録する。

関門1: Cookbook *** not found. が発生した時

knife solo prepare ***@hostname.local を実行したあと、 knife cook に入ろうとしたところで ERROR が発生してしまいなかなか前へ進めない。

Uploading the kitchen...

Generating solo config...

Running Chef...

Starting Chef Client, version 11.12.0
Compiling Cookbooks...

Running handlers:
[2014-10-27T21:35:08+09:00] ERROR: Running exception handlers
Running handlers complete

[2014-10-27T21:35:08+09:00] ERROR: Exception handlers complete
[2014-10-27T21:35:08+09:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
Chef Client failed. 0 resources updated in 2.393915796 seconds
[2014-10-27T21:35:08+09:00] ERROR: Cookbook users not found. If you're loading users from another cookbook, make sure you configure the dependency in your metadata
[2014-10-27T21:35:08+09:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
ERROR: RuntimeError: chef-solo failed. See output above.

Cookbook users と記載されている部分は、今回適用しようと思った recipe の名前。これが見つからないとエラーになっている。

対処方法

CookbookへのPATHが通っていない。結果だけ見ると単純すぎる…

cookbook_path    ["cookbooks", "site-cookbooks"]

昔作った .chef/knife.rb の表現が古くなっていたようで、そちらのはこの記載が入っておらずなかなか気づかなかった。既存の .chef を削除した後、knife solo init . を実行すると新しいものが作られる。

関門2: metadata.rb内のlong_descriptionが邪魔する

Running handlers:
[2014-10-27T21:42:08+09:00] ERROR: Running exception handlers
Running handlers complete

[2014-10-27T21:42:08+09:00] ERROR: Exception handlers complete
[2014-10-27T21:42:08+09:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
Chef Client failed. 0 resources updated in 2.06716861 seconds
[2014-10-27T21:42:08+09:00] ERROR: No such file or directory - /home/****/chef-solo/cookbooks-3/ssh/README.md
[2014-10-27T21:42:08+09:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
ERROR: RuntimeError: chef-solo failed. See output above.

対処方法

knifeコマンドでrecipeを作成すると自動生成されるmetadata.rb内には以下の記述がある。小規模・個人的な利用なので不要なファイルは削除してしまった為、ここでファイルが存在しないとエラーになっている様子。

long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))

long_descriptionは行ごとごっそり削除してしまうことにする。

関門3: SSLチェックのエラーが出る

[2014-10-27T21:56:04+09:00] WARN:
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
SSL validation of HTTPS requests is disabled. HTTPS connections are still
encrypted, but chef is not able to detect forged replies or man in the middle
attacks.

To fix this issue add an entry like this to your configuration file:

```
  # Verify all HTTPS connections (recommended)
  ssl_verify_mode :verify_peer

  # OR, Verify only connections to chef-server
  verify_api_cert true
```

To check your SSL configuration, or troubleshoot errors, you can use the
`knife ssl check` command like so:

```
  knife ssl check -c /home/****/chef-solo/solo.rb
```

* * * * * * * * * * * * * * * * * * * * * * * * * * 

対処方法

ここは素直にrecommendされている一行 ssl_verify_mode :verify_peer.chef/knife.rb に追加する。

そして成功へ

なんとか実行完了しました。黒いターミナルの上に輝く緑の文字が嬉しい。

安全にリモート接続!TeamViewerでセキュリティ最優先の設定

自宅では基本席にMac生活なのですが、Windowsでしかできないソフト利用も一部あり、仕方なく仮想マシンとしてWindowsを起動しています。今まではMacMicrosoft純正の「Microsoft Remote Descktop」を利用していたのですが、MSパッチの都合でRDPに不具合が発生してしまい、代替方法を検討しました。

結論を先に記すと、TeamViewerでいいかなって思いました。小生はTeamViewerのような第三者設備を経由する仕組みが嫌い(気持ち悪い)で極力避けるようにしているのですが、設定次第でそこそこ安全に使えるかなと思ったので、利便性とのバランスが良いと感じています。

Microsoft Remote Descktop 代替の選定

まず、定番のVNCでいいかなと思って数年ぶりに「UltraVNC」を利用。ある程度の操作はできるのですが、テキスト入力が非常に不安定で、なぜかVNCの画面転送が止まってしまいます。

  • Windows側環境: Windows 7 (64bit), UltraVNC, 既にTeamViewerも存在
  • Mac(Client)環境: Chicken of the VNC

競合しているのかもしれませんが、あまり本気でないところの事象切り分け・深堀をするのが面倒だったので、さっさと次を探すことにしました。次に、稀に外出先から繋げられるようにインストールをしておいたTeamViewerをもっと安全に使えないか?という方向で検討をはじめます。

TeamViewerをセキュアに利用する設定

ソフトウェアそのものの安全性は、公式ページの情報を鵜呑みにすると、セキュリティ要求の高い銀行でも採用されている実績などを踏まえると信用してよいと考えられます。(信じるしかない、とも言う)

TeamViewerは「使用中のID」「パスワード」「個人的なパスワード」「サインイン用のID(電子メールアドレス)」など、いろいろあってわかりにくいです。小生も完全には理解していませんが、この複雑さは第三者から接続して支援してもらうようなシーンも想定されていることが要因と考えられます。

自分だけがセキュアに使う前提ならば、もっとシンプルに考えられます。以下考え方を記載します。

目指すTeamViewerリモートアクセス環境

以下のポリシーで検討します。

  • 自分だけが使えればいい
  • 何より安全(セキュア)を優先して考えたい
  • 自宅内(同一LAN)でリモート接続する場合はインターネット経由にしたくない

これらが目標となります。

先ほどいろいろなID/パスワードがあると記載しましたが、上記のポリシーに当てはめると次のように使い方を整理できます。

設定項目 利用方法
使用中のID 遠隔操作受付用。本来、他人からの接続を受け付ける時などに相手へ教える情報となるが、自身での利用に限れば不要。設定上無効にはできないので無視することになる。
パスワード ^
個人的なパスワード 該当デバイスへのリモート接続時に入力するPW
WebサインインID(メアド) アカウントへのログインに利用。アカウントには複数端末がひも付き一覧化されるので、それを選択することでリモート接続できる。かつホワイトリストで接続元を限定する際にも利用(詳細後述)
WebサインインPW ^

part1: ここは絶対にやっておきたい!設定

TeamViewerはもともと他人からも接続しやすいようになっているので不安が残ります。そこで、下記の通り二重の対策で他人からの接続リスクを最小限にします。

  1. TeamViewerをインストールした端末へのリモート接続は、許可したアカウントでしか許可しない設定とする(ホワイトリスト
  2. 遠隔操作受付用のパスワードは最もセキュリティレベルが高い設定とする(※ホワイトリストにした時点で事実上意味はなくなりますが念のため)
  3. 個人的なパスワードもそれなりの強度にしておく

1のホワイトリスト設定が最も重要なところで、ここさえ守れればその他は補足的なものになりますが、しっかりやるに越したことはありません。

141025-0004

パスワードのセキュリティは「非常に安全(10文字)」を選びます。また、最下部の「設定」ボタンからホワイトリスト設定を行います。

141025-0005

自身のアカウント(Webサインインのメアド)を入力して接続を限定します。

141025-0003

遠隔操作用のパスワードは、見ての通り記号を含む強固なものになっています。

part2: 必須ではないけど可能ならばやりたい!設定

part1でどんなに遠隔操作対策をしたところで、肝心のアカウントが乗っ取られては意味がありません。ここのパスワードを強固にしておくのは当然ですが、加えて二段階認証もやっておくとベストでしょう。

二段階認証は、Google, Evernoteなどと同様の一般的な方法(Google認証アプリ)が利用可能です。別の話題になりますが、Google認証アプリは1デバイスにしかインストールできないため、複数デバイスでワンタイムコードを確認できるAuthyアプリもお勧めです。

141025-0001_2stp

141025-0002_2stp

自宅内での利用の場合はLAN接続で高速化

これが結構びっくりしたのですが、TeamViewerでは接続先としてLAN内のIPアドレスも指定できるようです。てっきりインターネット経由が必須だと思い込んでいたので非常に有り難い。

LAN接続を許可するには、「受信LAN設定」の項目を同意としておく必要があります。

設定項目 動作
非アクティブ化済 LAN接続はできず、インターネット接続のみになる
同意 LAN接続とインターネット接続が共存できる
同意のみ LAN接続しか許可しない

141026-0001

141025-0006_lan

余談:徹底的にセキュリティを追い求める場合

自宅へのVPN接続+RDP/VNC等でのリモートアクセスがベストでしょう。しかし、ガチガチすぎるが故に利便性の低下は否めません。

  1. 外出先のスマホからリモート接続したいと思った場合、一度VPN接続をしたうえでRDPアプリの起動を行うので、一手間多い
  2. VPN接続時のバッテリ消費は少なからず早くなる
  3. RDP接続先のブラウザでFlashのページ(艦これなどのFlashゲームなど)を表示すると、かなりカクカクして話にならない(TeamViewerだと割と快適。RDPの問題であって、VNCでは問題ないかもしれない)
  4. そもそも、VPN接続用のLinuxサーバを建てるのも面倒。VPN可能なルーターを買ってもよいが、製品縛りが発生してしまうので嫌。

これらは、実際にこの環境で運用を続けてきて常々思っていた不満でもあります。致命傷とまではいかなかったのでそのまま使い続けていましたが、今回RDP接続が不良になったのを機に完全に見直すこととしました。

TeamViewerの場合はそれぞれの環境にアプリをひとつインストールするだけなので非常に楽ですね。本記事に示したとおり、セキュリティを意識した設定を行えばそれなりに安全ですので、この選択肢は非常にアリだと思いました。