コンテナでC言語の開発環境を構築する

# この記事について

C言語の開発環境には様々な依存ライブラリをインストールする必要があります。そのため開発者それぞれの環境を統一するのが大変です。これまで環境を統一するためには、

- 開発者全員が同じマシンの開発環境を使って開発
- 開発環境構築手順書を作成し、配布する
- 開発環境のVMイメージを配布する

のような面倒なことをする必要がありました。
でも、今はコンテナという便利な物があります。
筆者もこれまでAWSのEC2上に開発＆試験環境を作り、開発者へ提供していました。現在はDockerfileやDockerイメージを提供しています。ここではDockerfileとイメージの作成し、その利用方法を説明します。

# 前提

Docker for Mac／Windowsなど,Dockerコンテナが動作する環境がインストール済みである事。あと、ほんの少しDockerやLinuxの知識が必要です。
尚、この記事ではmac上での作業内容を記載しています。Windowsで作業する場合はパス等、適宜読み替えてください。

# ゴール

この記事では、DockerコンテナのCentOS上で次の事ができるようになります。

1. viでC言語のソースが書ける
2. gcc,makeを使って、C言語のプログラムがコンパイルできる
3. コンパイル済みの実行ファイルが実行できる
4. コンパイル済みの実行ファイルがgdbでデバッグできる
5. （おまけ）java,pythonも使える

# Dokerfile作成

dokerファイルはコンテナイメージを作るための設計図です。
コンテナを使う以前は、OSやライブラリ等のインストールを手作業で行っていました。その手順をメモする替わりにDockerfileに書いておけば、簡単に再現できます。 
これぞまさしく **Infrastructure as Code(IaC)** の第一歩ですね。

### まずイメージを作る場所を決める

まず、イメージを作る場所を決めます。そこにDockerfileを作成／配置します。

```shell
> cd ~/docker/
```
### Dockerfile

完成版は[こちら（Dockerfile）](https://github.com/Be-cricket/c_dev_env_for_qiita/blob/master/docker/Dockerfile)から
Dockerfileにいろいろ記載してきます。
 
#### ベースイメージ

好きなOSを選んでください。
ここでは開発環境としてCentOS 7を選びます。

```shell
From centos:7
```
#### ワーキングディレクトリ

インストール作業するディレクトリを指定します。

```shell
WORKDIR /root
```
#### モジュールのインスール

yum で好きなパッケージをインストールしてください。
C言語で開発するなら、yumのgroupinstallが便利です。イメージが大きくなりますが、開発環境なら問題ないでしょう。

```shell
RUN yum -y update && \
    yum -y groupinstall "Development Tools"
```

その他はご自由に！
[こちら（Dockerfile）](https://github.com/Be-cricket/c_dev_env_for_qiita/blob/master/docker/Dockerfile)には、普通に使いそうなものを記載しています。

#### イメージへファイルの取り込み

ホストPCのファイルシステムからイメージ内へファイルを取り込みます。

```shell
COPY .bashrc /root/
```
今回はshell実行時に環境変数が設定された状態にしたいので、[.bashrc](https://github.com/Be-cricket/c_dev_env_for_qiita/blob/master/docker/.bashrc)をイメージに追加しています。
必要なファイルがあったら
　COPY [ホストPCのファイル] [Dockerイメージ内の位置]
で追加してください。

## Dockerイメージ作成
Dockerファイルのあるディレクトリ（ここでは ~/docker/)に移動してビルドします。

```shell
> cd ~/docker/
> docker build -t c_dev_env .
```
ちょっと時間がかかります。

```shell
> docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
c_dev_env                 latest              aa8cc7f4372f        About an hour ago   1.45GB
```
これでDockerイメージ「c_dev_env」の完成です。

> 参考
> DockerHubに登録（push)するためには、タグ(-tオプション)に[organization]を付加する必要があります。
> `docker build -t [organization]/c_dev_env .`
 
#Dockerコンテナ実行

この記事で一番重要な内容部分です。gdbコマンドがシステムコールを使用しているため、適切なオプションを付けないと実行できません。

` docker run -it --name "my_dev_env" --cap-add=SYS_PTRACE --security-opt="seccomp=unconfined" c_dev_env /bin/bash `

**オプション、パラメタの説明**

|オプション／パラメタ|説明       |
|:--------------|:---------|
|-i       |(--interactive)stdinを使用する |
|-t       |ttyを有効にする　　　　　　　　　　|
|--name   |名前をつける。ここでは"my_dev_env" |
|--cap-add=SYS_PTRACE|Linux Capability ptrace()システムコールを許可。 [（参考）Man page of Capabilities](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/capabilities.7.html)|
|--security-opt="seccomp=unconfined"|seccompという仕組みでもシステムコールが制限されているため、それを回避。[(参考)seccompプロファイルを使ってdockerのシステムコールを制御](https://blog.kazu69.net/2016/06/27/control_system_callof_docker_using_secomp/)|
|パラメタ１|イメージ名。ここでは c_dev_env を指定  |
|パラメタ２|実行コマンド。ここでは/bin/bash を指定  |

> -v [ホストPCのディレクトリ]:[コンテナのディレクトリ]　で、マウントしておくと便利です。ホストPCの慣れたエディタで開発できます。


```shell
> docker run -it --name "my_dev_env" --cap-add=SYS_PTRACE --security-opt="seccomp=unconfined" c_dev /bin/bash
bash-4.2# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
bash-4.2#
```

これで実行環境が完成しました。

#### プログラム作成&実行

それでは簡単なプログラムを作って、実行してみましょう。
Dockerイメージ内 /root/sampleの下へ

```shell
bash-4.2# cd /root
bash-4.2# mkdir sample
bash-4.2# cd sample
bash-4.2# vi hello.c
```

プログラム作成 [hello.c](https://github.com/Be-cricket/c_dev_env_for_qiita/blob/master/src/hello.c)

```c:hello.c
#include <stdio.h>

int main(void)
{
  int sum = 0;
  sum++;
  printf("Hello, World!\n");
  printf("sum = %d\n", sum );
  return 0;
}
```

そしてコンパイル
` gcc -g -o hello hello.c ` 
-g：デバッグオプションを忘れずに！

```shell
bash-4.2# gcc -g -o hello hello.c
bash-4.2# ls -la
total 24
drwxr-xr-x 2 root root 4096 Jun 23 14:24 .
dr-xr-x--- 1 root root 4096 Jun 23 14:23 ..
-rwxr-xr-x 1 root root 9528 Jun 23 14:24 hello
-rw-r--r-- 1 root root  136 Jun 23 14:24 hello.c
bash-4.2# ./hello
Hello, World!
sum = 1
```
ちゃんと実行できています。

#### デバッグ

今度は、gdbでデバッグしてみましょう

```shell

bash-4.2# gdb hello
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
〜　省略　〜
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/sample/hello...done.
(gdb) 
```
エラーもなくプロンプトが出てきました。
続いて、ブレークポイント設定＆ステップ実行

```shell
(gdb) l <---リスト表示
1	#include <stdio.h>
2
3	int main(void)
4	{
5	  int sum = 0;
6	  sum++;
7	  printf("Hello, World!\n");
8	  printf("sum = %d\n", sum );
9	  return 0;
10	}
(gdb) b 6  <---６行目にブレークポイント設定
Breakpoint 1 at 0x40056c: file hello.c, line 6.
(gdb) r　　 <---実行
Starting program: /root/sample/hello

Breakpoint 1, main () at hello.c:6
6	  sum++;  <---ブレークポイントで止まった！
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.5.x86_64
(gdb) n   <---ステップ実行
7	  printf("Hello, World!\n");
(gdb) p sum　<---変数内容表示
$2 = 1
```
ブレークポイント設定、ステップ実行問題なくできています。

#### Dockerコンテナから抜ける
最後に、DockerコンテナのLinuxから抜けるには Ctrl+p ,Ctrl+qです。
もう一度つなぐ時は、 `docker attach [タグ名]`

```shell
> docker attach my_dev_env
bash-4.2#
```
# まとめ
**Infrastructure as Code(IaC)** は大規模システムの環境構築、特にパブリッククラウドやコンテナオーケストレーション環境では必須の技術要素です。しかし、こんな身近な環境でも体感できる事に、少し感動しました。（当たり前なのですが）

- インストール手順書をDockerfileで書く
- 環境構築手順書をPlaybookに書く

これも**Infrastructure as Code(IaC)**の重要な要素であり、これだけでも[トイル(
SRE サイトリライアビリティエンジニアリング 5章)](https://www.oreilly.co.jp/books/9784873117911/)を確実に減らすとことができる気がします。ただし、Dockerfile職人とPlaybook職人による属人化には気をつけて。