近年ソフトウェアのクラウド化に伴い「Docker」への注目度はますます高まってきています。弊社でも「Webアプリケーションのステージング環境」、「開発メンバー間の開発環境の差異をなくす」といった目的でDockerを使用する機会が増えてきました。Web系に力を入れている開発言語は、優秀な統合開発環境のおかげでデバッグの実施コストにおいてホストOSとDockerコンテナでは大きな差はありません。しかしながら特にC/C++のコンテナ環境における効果的なデバッグ手法については情報が不足しているように見受けられます。

そこで今回は「Dockerのしくみ」、そして「Dockerコンテナ上で動作するアプリケーション」を対象に、デバッグ手法のひとつとしてDT10でテストレポートを取得する一例を紹介します。

Dockerとは

Dockerとは、Docker Engine というアプリケーションの起動に必要なアプリケーション本体・依存するライブラリ・設定ファイル等をコンテナという環境にまとめ、コンテナエンジンの上で動作させるコンテナ仮想化技術と、コンテナの配布をするためのDocker Hub というSaaSで構成されたプラットフォームです。では、どういった利点があるのか、それはどのようなしくみによりもたらされるのでしょうか。

Dockerを使用する利点

Dockerを使用する利点として次の点が挙げられます。

  1. 軽量であること
  2. 環境構築に要するコストが削減できること
  3. 動作環境の違いによって生じる不具合を抑制できること
軽量であること

“Docker メリット”などで検索すると、しばしば下図のような仮想マシンとの比較を見かけるかと思います。


仮想マシン上でアプリケーションを動作させるためには、「VMWare」や「VirtualBox」といったハイパーバイザーが必要になります。ハイパーバイザーが動作させる仮想マシンにはアプリケーションが稼働するためのライブラリやOS環境が入っており、稼働させる仮想マシンを増やすほど確保すべきディスク容量やハードウェアリソースへのアクセスを制御するハイパーバイザーの管理コストが跳ね上がります。
これに対してコンテナは仮想マシンとは異なり、コンテナエンジンを介してホストOSのカーネルを使用します。コンテナ内で動作しているプロセスはホストOS上で直接動作しているプロセスの一つであり、ハイパーバイザーの実行に伴うオーバーヘッドがない分アプリケーションの動作や処理速度が比較的高速です。

環境構築に要するコストが削減できること

Dockerコンテナの考え方のうちの一つに”イメージ”という考え方があります。DockerイメージはUFS(union file system)というファイルシステムを重ねられるレイヤとして扱い、複数のレイヤが重なったものをあたかも一つのファイルシステムのように見せる技術で構成されています。Dockerイメージにレイヤとして扱われているファイルシステムは読み取り専用のファイルシステムですが、Dockerが持つDockerイメージをDockerコンテナに変換する機能を用いることでイメージの上にコンテナとして読み書き可能なファイルシステムが追加されます。

また、DockerにはDockerfileというものがあります。DockerfileはDockerイメージを自動で作成するための手順を記述したテキストファイルで、DockerにはこのDockerfileからDockerイメージを自動で作成する機能も持っています。ユーザーはこれに以下の項目を記載します。

・ベースとするイメージ
・アプリケーション実行のために必要な処理
・アプリケーションの実行命令

この機能を用いて目的のアプリケーションを実行するためのDockerイメージを作成できます。

(*1) DockerfileからDockerイメージを作成
(*2) DockerイメージからDockerコンテナを作成
(*3) DockerイメージからDockerコンテナを作成・起動
(*4) 停止中のDockerコンテナを起動することも可能
(*5) 稼働中のDockerコンテナを停止することも可能

これらの仕組みを利用することで、開発者はDockerがインストールされているマシンさえあれば、共通のDockerfileを使ってほかの開発者と同じ動作環境を構築できます。

動作環境の違いによって生じる不具合を抑制できること

各開発者が決められた手順書にしたがって環境構築をしたとき、些細なミスでプログラムが動かない、挙動がおかしいといったことはよくあることです。2点目のメリットに関連することですが、開発者が「共通のDockerfileを使ってほかの開発者と同じ動作環境を構築する」ということはDockerの機能を使ってDockerfileからDockerコンテナを作成することであり、その間開発者の作業は介在しません。そのため「Aさんの環境では○○な動作をする不具合があった」「Bさんの環境ではそれが発生しない」といった事象が少なくなり、不具合が見つかった場合これが動作環境に起因するものなのか、アプリケーションによるものなのか、問題の切り分けがしやすくなります。

以上の3点がコンテナ技術の代表的な利点として挙げられますが、もちろんコンテナ技術にも欠点はあります。ホストOSのカーネルを使用するという特性を持つということは、裏を返すとホストOSと同じカーネルを実行しなければならないという制約が付きます。例えばWindowsホストOSにおいてWindows OSのコンテナとLinux OSのコンテナを同時に稼働させることはできなかったり…手持ちの環境でどのように運用するかは慎重に検討する必要があります。

DockerをDT10でデバッグする

DT10 は弊社で開発・販売している動的テストツールです。主な機能として、プログラムの実行経路情報(以降よりトレース情報と呼びます)の取得やトレース情報の解析機能があります。その導入では用途に合ったドライバ関数をターゲットアプリケーションに組み込むだけである、という点から導入コストの低さに定評があります。Dockerの環境で使用するには、Dockerfile・ドライバ関数を定義したソース一式が共有できれば、誰でも手軽にターゲットアプリを動かしながらデバッグすることが可能です。では、DT10を導入してどのような情報が得られるのか、実際に見てみたいと思います。

テスト対象とするアプリ仕様

今回はリクエストに対して簡単なレスポンスを返すC++で実装されたサーバーアプリケーションを例に挙げます。基本の動作仕様は以下の通りです。

  • サーバーがリクエストを待機するポートに対してGETリクエストを送信するとボディを”Heartland Data”とした200レスポンスを返す
  • ほかのリクエストメソッド(PUTやPOST)を受け取った場合は未実装を示す501レスポンスを返す
  • Unix標準ライブラリやシステムコールを使用しているため、動作環境はLinux OS

以下curlでHTTPリクエストを実行した例です。

$ curl -i localhost:3001
%   Total %   Received % Xferd Average Speed  Time     Time     Time     Current
                               Dload   Upload Total    Spent    Left     Speed
100 15    100 15       0 0     192     0      --:--:-- --:--:-- --:--:-- 192     HTTP/1.1 200 OK
Date: Tue, 03 Mar 2020 10:21:13 GMT
Server: MYHTTP/1.0
Connection: close
Content-Length: 15
Content-Type: text/plain

Heartland Data

また、ソース一式の構成は下記の通りです。

今回使用するDockerfileはUbuntuの最新のイメージをベースにアプリケーション実行のために下記の手順を行うものです。

FROM ubuntu:latest

# ビルド環境の構築
RUN apt update && apt install -y build-essential

# ソースのビルド
WORKDIR /home/project
COPY source source
COPY makefile makefile
RUN make

# コンテナ起動時のコマンド
ENTRYPOINT ["./myapp"]
Dockerコンテナに対してDT10を導入する

アプリケーションからDT10のトレースデータを取得するには、まずテストポイントと呼ばれる通過情報出力用のコード(≒pritntfデバッグで使用するprint文、下図の赤い文字列)を挿入します。関数の入り口・出口・各分岐(if, for文など)に対して、DT10が自動で挿入してくれます。実際にアプリケーションが実行されると、テストポイントを通過した際にトレース情報がリアルタイムに出力されます。

トレースデータの出力にはテストポイントの通過時にターゲットのI/Fを通してPCにデータを送る必要があります。DT10は様々なI/F(接続方式)に対応しており、今回はEthernetを採用します。以下のような形で、サーバアプリからDT10のインストールされているPCにデータを送信します。

最後に、トレースデータを採用した接続方式から出力するための処理を追加します。この処理はドライバ関数と呼ばれ、ドライバのソースコードをターゲットのコードと一緒にビルドします。ターゲットのOSや接続方式別に弊社でサンプルドライバを用意しており、今回のターゲットはUbuntuベースなので、Linux用のEthernet接続用のドライバを使用します。

これで準備は完了です。実際にDockerコンテナで稼働しているアプリケーションのトレース情報を、DT10を使用してリアルタイムに取得できます。そして、アプリケーションの実行終了後、取得したログをDT10で解析することで様々な視点から評価することができます。今回は3つの例を見てみましょう。

DT10を使ったDockerコンテナのデバッグ
<実行時の処理や経路、変数値>

DT10では、トレース情報を以下のようなリストで表示します。これにより、コンテナが起動しアプリケーションが実行された際に、どのような処理がどのような順番で実行されたのかという実行経路が分かります。

例えば、アプリケーションが落ちてしまったというときは、どこで落ちたのか、そこに至るまでどのような実行経路だったのかを確認できます。変数値も併せて確認でき、例えばHttpリクエストを保持している変数をモニタリングすることで、そのリクエストを受けたときの処理は適切かといった確認も可能です。

<コンテナ内のプロセスの遷移>

DT10では、上記の実行経路や変数値だけでなく、pidを取得することもできます。上記のリストのイベントIDがpidを表し、リストはpidごとに色分けすることができます。実行経路をただトレースするのではなく、どのプロセスによって実行されているか、どこから切り替わっているのかなども同時に確認することができます。これらをより俯瞰するために、以下のようなビューも用意されています。

上記のふたつによって、動作確認の時間を短縮したり、より少ない工数で不具合の原因を特定できます。

<複数のコンテナの動作を同一時間軸上で確認する>
単一の簡単なアプリケーションを例として挙げましたが、昨今のソフトウェア業界では複数のサーバーアプリケーションが同時に稼働しており相互にやり取りを行う、といったシステムも珍しくありません。そのような場合でも、以下のような構成にすることで、複数のコンテナの情報を同一のトレース情報から確認することが可能です。

これにより、例えばコンテナ間で通信が発生したときの、ある変数の値の妥当性やその際の実行経路は問題がないか、などの確認を互いのコンテナで行えます。また、実行時間も解析できるので、通信時の思わぬボトルネックが発生している、といったことも分かります。

解析の例をいくつか挙げましたが、Dockerで事前に設定をしたり必要なコマンドを打ち込んだり・・・といった必要がないため、ログの収集・確認の敷居を低くできるという大きなメリットがあります。導入は手軽でありながらも、実行経路や実行時間、プロセスの遷移、変数値のモニタリング、これらをたった1回のログ取得でできるのも大きな特長です。
また、トレースデータをファイルに書き出す方式を使用すれば、ローカル環境だけでなく客先で発生した不具合に対しても動作ログの確認ができます。
参考:客先でしか発生しない不具合の解析

まとめ

Dockerの普及率が高まり、様々な開発言語向けにデバッグ手法が公開されております。しかし開発現場の事情によってはそういった既存の手法が最適ではなかったり、適用に至ることができない場合もあるのではないでしょうか?開発現場に合ったデバッグ手法の選択肢の一つとして本稿がお役に立てれば幸いです。


DT+を無料でお試しいただけます!

今回コンテナのデバッグに使用したDTシリーズは無料トライアルも可能ですので、
ご興味がある方はお気軽にお問い合わせください。

「動的テストツールDT+」の詳細はこちらから