Delphi/RAD Studio 10.2のLinux向け環境をDockerコンテナで作る

# この記事の目的：いまどき、仮想マシンではなく、コンテナでLinux向けの開発環境を作りたいので、そのための操作方法をざっくり紹介します

Delphi/RAD Studio 10.2 Tokyo では Linux 向けのサーバ用アプリ開発も可能になりましたが、IDE自体はWindowsで動作しますので、Linux向けの開発では、Linuxがインストールされた別の環境(別のコンピュータまたは仮想マシン)が必要です。あるいは Windows 10 で利用可能となった Windows Subsystem for Linux を使いたいと考えるケースもあるかもしれません。

しかし物理マシンや Windows Subsystem for Linux で作業環境を作るのは、環境が汚れた場合の切り戻しに手間が掛かります。仮想マシンで環境を作ればスナップショットが取れるので切り戻しはカンタンですが、仮想マシンの実行にはCPUやメモリ、ストレージをそれなりに消費しますので、少しもったいない気がします。

そこでここでは Delphi Linux 向けの作業環境を Docker コンテナで作ることにします。また、この作業を通じて、Docker の初歩的な操作が学べると思います。

# Dockerを使うメリットとは？

仮想マシンにくらべると、こういう点が便利だと思います。

- コンテナの準備はコマンドをちょっと実行するだけで、ほとんど自動でできる
- 実行に必要なディスク容量が少ない
- 動作時のCPUリソース、メモリが少ない
- 必要なときにすぐに起動できる（ほぼ一瞬）

# 注意：インストールの前提となる環境について

Docker を利用する場合は以下の点に注意してください。

- macOS の場合は Docker for Mac をインストールすればOKです
- Windows 10 64-bit では Docker for Windows が利用できます。ただしこれはHyper-Vと組み合わせる必要がありますので、VMware　や VirtualBox をご利用の場合は Hyper-V と排他のため利用できません。32-bit Windows ではHyper-Vが提供されないため利用できません。
このようにWindowsでHyper-Vが利用できない場合は、普通に仮想マシン上にLinuxをセットアップしたほうがよいでしょう。Windows Subsystemfor Linuxで Dockerを動かすという方法もあるにはありますが、ちょっぴり裏技的な方法なので、ここでは紹介しません。

あるいはLinux上のDockerでLinuxコンテナを動かすという方法もあります。いまどきのサーバ側アプリケーション開発では様々なミドルウェアやライブラリを組み合わせることがあり、サーバに載せたい機能の組み合わせによってはちょっと複雑な構成になったりしますが、それぞれの機能ごとにコンテナ化すれば互いに影響を及ぼすことなく同居させることもできます。同じようなことを仮想マシンで行ってもよいのですが、仮想マシンよりも軽いリソースで動くコンテナのほうが扱いやすいです。

# 実際にやってみよう

## 注意：この記事で説明していないこと＝dockerのインストール

Docker for Mac や Docker for Windows のインストール方法は説明いたしません。他に詳しい記事がいろいろあると思いますので、そちらをご覧ください。


## Dockerhub に登録済みのイメージを使ってみる

docker コマンドが利用可能な状態になっていたら、以下のように実行してください。

```
docker pull  kazinoue/delphi-ubuntu
```

これはDockerhub上のkazinoue/delphi-ubuntuイメージを取得する操作です。このイメージにはDelphiでのLinux開発向けに使いそうなパッケージが含まれています。名前から予想がつくと思いますが、これは私が作成して Dockerhub に登録したものです（このイメージの作成に関する情報は、この記事の末尾をお読みください）。


## イメージからコンテナを起動する

実行すると、なにやらダウンロードが行われている様子が確認できます。終わったら次のコマンドを入力して、イメージからコンテナを起動します。

```
docker run -it kazinoue/delphi-ubuntu
root@1fb3bb711caf:/#
```

このようにプロンプトが変わっているはずです。これはダウンロードしたイメージを元に実行されたコンテナの中にいるんです。ここで Linux のコマンドをいくつか実行できることを確認してください。確認できたら exit で抜けていただいてOKです。

```bash
root@1fb3bb711caf:/# ls -l
total 64
drwxr-xr-x   1 root root 4096 Sep 28 13:08 bin
drwxr-xr-x   2 root root 4096 Apr 12  2016 boot
drwxr-xr-x   5 root root  360 Sep 29 11:58 dev
drwxr-xr-x   1 root root 4096 Sep 29 11:58 etc
drwxr-xr-x   2 root root 4096 Apr 12  2016 home
drwxr-xr-x   1 root root 4096 Sep 28 13:09 lib
drwxr-xr-x   2 root root 4096 Aug  8 02:04 lib64
drwxr-xr-x   2 root root 4096 Aug  8 02:03 media
drwxr-xr-x   2 root root 4096 Aug  8 02:03 mnt
drwxr-xr-x   2 root root 4096 Aug  8 02:03 opt
dr-xr-xr-x 181 root root    0 Sep 29 11:58 proc
drwx------   2 root root 4096 Aug  8 02:04 root
drwxr-xr-x   1 root root 4096 Aug  8 02:04 run
drwxr-xr-x   1 root root 4096 Sep 28 13:08 sbin
drwxr-xr-x   2 root root 4096 Aug  8 02:03 srv
dr-xr-xr-x  13 root root    0 Sep 28 02:11 sys
drwxrwxrwt   1 root root 4096 Sep 28 13:09 tmp
drwxr-xr-x   1 root root 4096 Aug  8 02:03 usr
drwxr-xr-x   1 root root 4096 Sep 28 13:08 var

root@1fb3bb711caf:/# uname -a
Linux 1fb3bb711caf 4.9.93-linuxkit-aufs #1 SMP Wed Jun 6 16:55:56 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

root@1fb3bb711caf:/# cat /etc/issue
Ubuntu 16.04.5 LTS \n \l

root@1fb3bb711caf:/# exit
exit
$
```

exit で抜けることができましたが、抜けた環境は一時的に停止しているだけなので、再利用することができます。

## 一時停止中のコンテナに再度接続する

先ほどのコンテナ内のプロンプトに表示されていた16進数の値　1fb3bb711caf を用いて、以下のように実行してみます。（この文字列は個別のコンテナごとに変わりますので、実際の実行時に表示された値を用いてください)

```
$ docker attach 1fb3bb711caf
You cannot attach to a stopped container, start it first
```

stop　しているから、start　しないとダメ、と言われましたので、start してから attach してみましょう。ほら、このとおり。

```
$ docker start 1fb3bb711caf
1fb3bb711caf

$ docker attach 1fb3bb711caf
root@1fb3bb711caf:/# 
root@1fb3bb711caf:/# history
    1  ls -l
    2  uname -a
    3  cat /etc/issue
    4  exit
    5  history```
```

## 不要になったコンテナを破棄する

それでは、一旦抜けて、このコンテナを完全に破棄します。```docker ps -a```で停止中のコンテナを確認し、```docker rm``` で削除します。

```
root@1fb3bb711caf:/# exit
exit

$ docker ps -a
CONTAINER ID        IMAGE                                   COMMAND             CREATED             STATUS                      PORTS               NAMES
1fb3bb711caf        kazinoue/delphi-ubuntu                  "/bin/bash"         7 minutes ago       Exited (0) 2 seconds ago                        

$ docker rm 1fb3bb711caf
1fb3bb711caf

$ docker ps -a
CONTAINER ID        IMAGE 
```

## 不要になったイメージを削除する

でも、ダウンロードしたイメージを削除したわけではなく、イメージから実行されたコンテナを廃棄しただけです。なので、イメージはローカルのリポジトリに残った状態です。

```
$ docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
kazinoue/delphi-centos                  latest              b42d8297d62e        23 hours ago        726MB
```

ここで先ほどの docker run をもう一度実行すれば、新しいコンテナを起動できます。ですが、ここではこのイメージを一度削除してみることにします。

```
$ docker rmi b42d8297d62e
Untagged: kazinoue/delphi-centos:latest
Untagged: kazinoue/delphi-centos@sha256:91bb5d09b9145610c07a779a4d2cb0dc906c1a8b35b9551ef2455b1166c21649
Deleted: sha256:b42d8297d62e4f3e9300233c09ac25a71fb87762a06adf79aee9d82c5a72c60e
Deleted: sha256:ad1639f06a6184442fb2bc00f8c08999f654c1ffdaf09e82f0f48baf168b3f03
Deleted: sha256:1d31b5806ba40b5f67bde96f18a181668348934a44c9253b420d5f04cfb4e37a

$ docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
```

たしかに無くなりましたね。

## イメージが無い状態で docker run したらどうなるか？

では、イメージがない状態で、もう一度 ```docker run -it kazinoue/delphi-ubuntu``` してみましょう。どうなるでしょうか。

```
$ docker run -it kazinoue/delphi-ubuntu
Unable to find image 'kazinoue/delphi-ubuntu:latest' locally
latest: Pulling from kazinoue/delphi-ubuntu
3b37166ec614: Pull complete 
504facff238f: Pull complete 
ebbcacd28e10: Pull complete 
c7fb3351ecad: Pull complete 
2e3debadcbf7: Pull complete 
792e9529847a: Pull complete 
Digest: sha256:9fed92843224b98e1a59cb884071487cb1e9178b96f975039f807024b4c5d250
Status: Downloaded newer image for kazinoue/delphi-ubuntu:latest
root@c84025c0f11e:/# 
```

ダウンロードと実行が両方行われました。docker run は、指定されたイメージがローカルにない場合はDockerhubのリポジトリから自動的に取得して実行できるわけです。ですので、通常は docker pull を省略して docker run だけ実行しても大丈夫です。

## 作業状態をイメージとして保存する

現在実行中のコンテナには PAServer が含まれていません。なので PAServer をデプロイした状態を作成してみましょう。PAServerはDelphi/RAD StudioのIDEをインストールした環境からコピーすることもできますが、インターネット側からダウンロードして頂く方法もあります。

下記のページに Delphi/RAD Studio 10.2 や 10.2.3 向けのPAServerのダウンロードリンクがありますので、これをコンテナの中から直接ダウンロードしてみることにします。

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/Linux_でのプラットフォーム_アシスタントのインストール

作業内容はこんな感じ。

```bash
root@c84025c0f11e:/# cd /root

root@c84025c0f11e:~# wget http://altd.embarcadero.com/releases/studio/19.0/PAServer/Release3/LinuxPAServer19.0.tar.gz
--2018-09-29 12:25:06--  http://altd.embarcadero.com/releases/studio/19.0/PAServer/Release3/LinuxPAServer19.0.tar.gz
Resolving altd.embarcadero.com (altd.embarcadero.com)... 221.110.252.24
Connecting to altd.embarcadero.com (altd.embarcadero.com)|221.110.252.24|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6705446 (6.4M) [application/x-gzip]
Saving to: 'LinuxPAServer19.0.tar.gz'

LinuxPAServer19.0.tar.gz                100%[==============================================================================>]   6.39M  4.79MB/s    in 1.3s    

2018-09-29 12:25:08 (4.79 MB/s) - 'LinuxPAServer19.0.tar.gz' saved [6705446/6705446]

FINISHED --2018-09-29 12:25:08--
Total wall clock time: 2.3s
Downloaded: 1 files, 6.4M in 1.3s (4.79 MB/s)

root@c84025c0f11e:~# tar zxvf LinuxPAServer19.0.tar.gz
PAServer-19.0/
PAServer-19.0/linuxgdb
PAServer-19.0/paconsole
PAServer-19.0/paserver
PAServer-19.0/paserver.config

root@c84025c0f11e:~# ls -l
total 6556
-rw-r--r-- 1 root root 6705446 Mar 12  2018 LinuxPAServer19.0.tar.gz
drwxr-xr-x 2  501   80    4096 Mar  9  2018 PAServer-19.0

root@c84025c0f11e:~# ls -l PAServer-19.0/
total 25032
-rwxr-xr-x 1 501 80  3995296 Jul 18  2017 linuxgdb
-rwxr-xr-x 1 501 80  2858520 Mar  9  2018 paconsole
-rwxr-xr-x 1 501 80 18769024 Mar  9  2018 paserver
-rw-r--r-- 1 501 80       47 Mar  9  2018 paserver.config

root@c84025c0f11e:~# exit
```

これで　PAServer が展開された状態ですので、この状態をイメージにしてみます。これには ```docker commit``` を以下のように実行します。

```
$ docker ps -a
CONTAINER ID        IMAGE                                   COMMAND             CREATED             STATUS                      PORTS               NAMES
c84025c0f11e        kazinoue/delphi-ubuntu:latest           "/bin/bash"         8 minutes ago       Exited (0) 23 seconds ago                       sharp_hawking

$ docker commit c84025c0f11e delphi-ubuntu
sha256:5c74c0ae1bafde11a32870135111e7ff437457b102c9849edd9aa681f1f27e38

$ docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
delphi-ubuntu                           latest              5c74c0ae1baf        5 seconds ago       658MB
kazinoue/delphi-ubuntu                  latest              6281edcf9bc3        23 hours ago        626MB
```

これで、今度からは ```docker run -p 64210:64211 -it delphi-ubuntu``` と実行するだけで PAServer 入りのコンテナが起動できるようになります。ただしこの状態では PAServer 自体のプロセスは起動していませんので、シェルから起動させてください。

```-p 64210:64211``` の指定は、コンテナ内の 64211/TCP へのアクセスを、ホストマシン側の 64210/TCP にマッピングするための指定です。ではコンテナを起動して、実際にPAServerも実行してみることにしましょう。

```bash
$ docker run -p 64210:64211 -it delphi-ubuntu

root@39336bf60d5f:/# cd /root/PAServer-19.0/

root@39336bf60d5f:~/PAServer-19.0# ./paserver 
Platform Assistant Server  Version 10.2.1.13
Copyright (c) 2009-2018 Embarcadero Technologies, Inc.

Connection Profile password <press Enter for no password>: 

Starting Platform Assistant Server on port 64211

Type ? for available commands
>i
172.17.0.2
>v
verbose mode enabled
>
```

i を実行して表示されるIPアドレスがホストマシンのものとは違っていることに注意してください。また、リッスンポートが 64211 と表示されていることにも注意してください。これらの情報はあくまでコンテナ内での情報ですから、Delphi/RAD Studio IDE からの接続では利用できません。

# Delphi/RAD Studio の IDE から接続できるようにする

これは iOS/macOS 向けの開発と基本的に同じ手順ですのでDelphi/RAD Studioでクロスプラットフォーム開発を行った経験があれば問題無く進められると思います。


PAServerを初めて扱う場合は、下記の手順を参考に設定してください。

今回の設定では Delphi/RAD Studio の IDE からの接続は、[ホストマシンのIPアドレス]:64210 に対して行うことが必要です。デフォルトの 64211 ではありません。

Delphi/RAD Studio の IDE で メニューバーの "ツール" => "オプション" より "接続プロファイルマネージャ" を選択し、"追加(A)..." を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/7d9d4558-b4c6-f4e7-4ef7-6d3a45964a31.png)

プロファイル名、プラットフォームを選択して...

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/c7c65dab-8c82-6a76-8123-30761e374d0e.png)

PAServer 実行中の Docker コンテナが稼働するホストマシンのIPアドレスやホスト名と、ポート番号（今回は 64210）を指定します。接続テストで問題がなければ設定完了です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/ad4a2fb4-43c4-458e-6796-e273502b91e0.png)


さらに SDKマネージャーを選んで、64ビット Linux の SDK を PAServer 経由で IDE の環境にコピーします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/149350/ae7c8806-4ab5-9e4a-1874-eb8f69032605.png)

これで Delphi/RAD Studio での　Linux 向け開発環境が整いました。

# 実際にコードをビルドしてみよう。

試してみるコードはこんな感じでよいでしょう。

```bash
Writeln('Hello Delphi on Ubuntu 16.04 LTS Docker Container');
Writeln(TOSVersion.ToString);
```

Delphi/RAD Studio の IDEで「Delphi 向けのコンソールアプリケーションの新規作成」を選択すると空のプロジェクトが生成されますので、try - except の間に先ほどのコードを挟み込んで以下のような状態にします。

```Delphi
program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

begin
  try
    Writeln('Hello Delphi on Ubuntu 16.04 LTS Docker Container');
    Writeln(TOSVersion.ToString);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
```

なお、デフォルトのビルドターゲットは 32-bit Windows ですので、ターゲットに 64-bit Linux を追加、変更してビルドします。

実際に実行すると、Docker コンテナ側の表示はこのようになりました。

```bash
>  command_line: "/root/PAServer/scratch-dir/kazuhiro.inoue-DockerUbuntu/Project1/Project1" 
>Hello Delphi on Ubuntu 16.04 LTS Docker Container
Ubuntu 16.04.3 LTS #1 SMP Mon Nov 6 16:00:12 UTC 2017 (Version 4.9.60)
```

というわけで、Writeln で指定した内容が PAServer 側のコンソールに無事表示されています。Delphi for Linux の実行環境を Docker コンテナ上に作ることができました。

では Docker コンテナを抜けてみましょう。PAServer は ```q``` で終了できます。さらにここで ```exit``` するだけです。

# 参考：今回のコンテナの環境はどのように作られているか？

今回紹介したイメージは、下記の Dockerfile から作成しています。

https://github.com/kazinoue/delphi-ubuntu

この Dockerfile では、PAServer を含むイメージのビルド手順をコメントアウトしています。これを適切に修正して```docker build```すれば、PAServer入りのコンテナを全自動でクリーンビルドできます。その他、追加したいパッケージがあれば、それらを追記したDockerfileを作成して```docker build```することで、それそれの開発者にとって使いやすい作業環境を容易にカスタマイズできます。また不要になったコンテナやイメージはいつでも破棄できますので、ある意味気軽に環境が作れます。