docker-tramp.el でdockerコンテナのファイルを読み書きする


# はじめに

[docker-tramp.el](https://github.com/emacs-pe/docker-tramp.el) はdockerコンテナ内に存在するファイルを、Emacsから直接読み書きできるようにするライブラリです。使い慣れたEmacsでコンテナ内のファイルを編集できるようになるので編集効率が上がり、dockerでの試行錯誤が劇的にやりやすくなります。

この記事では docker-tramp.el の使い方について説明します。

# docker-tramp.el の必要性

dockerコンテナ内のファイルを読み書きしたいという状況には以下のような場面があると思います。

1. 他人が作成したdockerイメージの内容を理解する（ディレクトリ構成や設定ファイルの内容など）。
2. 独自のdockerイメージをビルドする際に設定ファイルやスクリプトなどを試行錯誤する。

1は初めて利用するツールやサーバアプリケーションをdockerでインストールしたような場面です。dockerイメージがどのように動作しているのかを把握するために、コンテナ内のファイル、ディレクトリを閲覧したくなります。

2は独自にカスタムしたdockerイメージをビルドする場面です。dockerイメージをビルドするときには、カスタムした設定ファイルや、エントリポイントとなる独自のスクリプトをイメージに含めたりします。それらのファイルの変更が意図通り動作しているか確認するのには試行錯誤が必要になります。

通常dockerでコンテナ内のファイルを読み書きするには

1. `docker exec -it sh` などでコンテナ内にログインする。
2. `docker cp` でコンテナ内のファイルをコピーし、ローカルで読み書きする。

の2通りの方法があります。しかし1の方法ではコンテナごとに利用できるプログラムが異なり、`vi` はおろか `less` さえインストールされていないコンテナもあります。2の方法はローカルのプログラムで読み書きできますが、コンテナに変更を反映するのに再度 `docker cp` でコンテナに書き戻す必要があり、手間がかかります。

このような場面で docker-tramp.el を使用すると、使い慣れたEmacsで直接コンテナ内のファイルを読み書きできるようになります。当然Emacsのシンタックスハイライトが効くのでとても読みやすくなります。Emacsの機能は大体そのまま透過的に使用できるので、検索、置換などなんでもありです。

# TRAMP

docker-tramp.el の説明に入る前にその前提である TRAMP について説明しましょう。

TRAMPの[マニュアル](https://www.gnu.org/software/tramp/)には

>TRAMP stands for "Transparent Remote (file) Access, Multiple Protocol".

と書かれています。その名の通りEmacsで様々なプロトコルを通じて透過的なリモートアクセスを実現する組み込みのライブラリです。例えばEmacsで `/ssh:user@host:/path/to/file` というファイルを開こうとすると、SSH経由で `user@host` というリモートホストにログインし、リモートホストに存在する `/path/to/file` というファイルを開くことになります。

`ssh` や `plink` といったSSH系のプロトコルは外部ライブラリを入れることなく利用できます。TRAMPは独自のプロトコルで通信するように拡張する仕組みがあり、この仕組でdocker向けに拡張したのが docker-tramp.el になります。

docker-tramp.el により、TRAMPの仕組みに乗っかってdockerホストからdockerコンテナのファイルを透過的に扱えるようになります。

# docker-tramp.el

それでは docker-tramp.el の使い方について説明します。

## インストール

docker-tramp.el は[MELPA](https://melpa.org/#/docker-tramp)に登録されているので、インストールは

```
M-x package-install docker-tramp
```

だけでOKです。

init.elに以下の設定を加えると docker-tramp.el が使えるようになります[^1]。

[^1]: バージョン2.3より前のTRAMPにはAlpineコンテナでうまく動作しないバグがあるため、docker-tramp-compat.el を使用している。TRAMPのバージョンが2.3以上になれば docker-tramp.el を直接使ってもよい。https://github.com/emacs-pe/docker-tramp.el/issues/11

```emacs-lisp:~/.emacs.d/init.el
(require 'docker-tramp-compat)
```

## コンテナ内のファイルにアクセスする

ではコンテナ内のファイルにアクセスしてみます。前提として[nginxコンテナ](https://hub.docker.com/_/nginx/)を2台動かしている状態とします。

```shell-session
$ docker run -d --name nginx1 nginx
$ docker run -d --name nginx2 nginx
```

コンテナ内のファイルを開くには `find-file` (`C-x C-f`) で

```
/docker:user@container:/path/to/file
```

を指定します。`user` はコンテナにアクセスするユーザで、省略するとコンテナのデフォルトユーザになります。`container` 部分はコンテナのIDかNAMESを指定できます。

`container` 部分で `<TAB>` や `C-i` を押下すると、現在稼働中のコンテナを補完してくれます。また `/path/to/file` 部分ではコンテナ内のファイル、ディレクトリ名を補完できます。

実際にコンテナ内の `/etc/nginx` にアクセスする様子をgifにしてみました。

![docker-tramp-example.gif](https://qiita-image-store.s3.amazonaws.com/0/11126/607aa239-4554-3d49-198c-47f04f07c664.gif)

コンテナ内のディレクトリ (`/etc/nginx`) にアクセスしたので、Diredでバッファが開きます。それから `conf` というキーワードで検索して `/etc/nginx/nginx.conf` ファイルを開くまでを記録しています。

このようにバッファ内の操作はただのEmacsなので、普段と変わらない使い勝手でコンテナ内のファイルを読み書きすることができます。

## Ad-hoc multi-hops

TRAMPには [Ad-hoc multi-hops](https://www.gnu.org/software/tramp/#Ad_002dhoc-multi_002dhops) という仕組みがあります。これは多段でTRAMPによるリモートアクセスする記法です。これができると何が嬉しいかというと、別マシンのdockerホストで稼働しているコンテナにアクセスできるようになります。

例えば以下のように `docker_host` 上で稼働している `container` に `localmachine` からアクセスしたいとします。

```
                            +-----------+
                            | container | <-----\
+--------------+          +---------------+   docker
| localmachine | --ssh--> |  docker_host  | ----/
+--------------+          +---------------+ 

```

この場合 `find-file` (`C-x C-f`) で

```
/ssh:user@docker_host|docker:container:/path/to/file
```

というファイル名を指定します。実際にアクセスしてみた様子は以下のようになります。

![docker-tramp-multihop-example.gif](https://qiita-image-store.s3.amazonaws.com/0/11126/616e3977-d6a9-92e2-929a-6bec33c2f727.gif)

これはWindowsマシンのEmacsからplink(Putty)でVagrant上のUbuntu（dockerホスト）にアクセスし、さらにUbuntu上で動作している `nginx1` コンテナにアクセスしています。

残念ながらmulti-hopsの場合は[コンテナ名が補完できない](https://github.com/emacs-pe/docker-tramp.el/issues/17)ようですが、一旦バッファを開いたあとならファイル名の補完自体はできます。

これでdockerホストが別ホストにある場合も安心です。

## TIPS

## コンテナ名で補完する

コンテナの補完をIDではなくNAMESでしてほしい場合は `docker-tramp-use-names` を `t` にします。

```emacs-lisp
(require 'docker-tramp-compat)
(set-variable 'docker-tramp-use-names t)
```

![docker-tramp-complete-names.png](https://qiita-image-store.s3.amazonaws.com/0/11126/c55a1eb5-5798-59d5-f249-1ef55bcb2ddc.png)

## `docker` コマンドは `sudo` なしで実行できる前提

docker-tramp.el は `sudo` なしで `docker` コマンドを実行できる前提で作られているようです。[ドキュメント](https://docs.docker.com/engine/installation/linux/linux-postinstall/#manage-docker-as-a-non-root-user)を参考にして `sudo` なしで実行できるようにしておきましょう。

# まとめ

docker-tramp.el を利用してdockerコンテナ内のファイルにアクセスする方法を紹介しました。docker-tramp.el によってdockerでの試行錯誤がやりやすくなるので、非常におすすめです。

## 環境

本稿の確認に使用した動作環境は以下のとおりです。

```shell-session
$ emacs --version
GNU Emacs 25.3.1
Copyright (C) 2017 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named OPYING.

$ docker --version
Docker version 17.09.1-ce, build 19e2cf6

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.3 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.3 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
```

現状docker for WindowsのようなWindows環境ではうまく動作しないようです。https://github.com/emacs-pe/docker-tramp.el/issues/7

Windows環境は以下のとおりです。

```
エディション: Windows 10 Pro
バージョン: 1709
OSビルド: 16299.125
```

Docker for Windows

```
Version: 17.09.1-ce-win42 (14687)
Channel: stable
Commit: 3176a6a
```