#ここアジャに沢山の勇気と感謝をもらった話。~"ここはウォーターフォール市、アジャイル町"を読んでの感想~

年明けから、数少ない通勤時間で少しずつ読み進めていた ここアジャ を読み終えたので、感想を書きます。

www.shoeisha.co.jp

本の概要

本書は、物語形式のページと、その物語で出てきた概念や手法の具体的な説明のページが章ごとにあります。
とある企業の情シス部門の運用チームが主役で、「無力感が蔓延しているチーム」から「自己組織化されたチーム」にセイチョウしていく物語を通じて、アジャイルな組織の作り方を学ぶことができます。

開発スタイルとしてウォーターフォール型を採用しているチームがアジャイルの手法を実践することでどうセイチョウできるのか、にフォーカスを当てており、ウォーターフォールからスクラムへの移行はテーマではありません。

著者である沢渡あまねさんの「チームを改善しようとした結果、アジャイルな手法を取り入れていた」という体験を、まさに追体験できる物語だなと思いました。

具体的な内容としては、タスク管理手法としてのチケット管理や、ふりかえりのススメ、ホワイトボードやオープンスペースを取り入れてみることや、顧客価値創造のためのワンチーム、などなど……。
単なる手法の紹介だけでなく、手法を取り入れる際の上司やチームメンバーとのコミュニケーションの取り方も交えて解説されているため、「これなら自分のチームでも小さく試していけそう」という実感を持ちながら読み進めることができました。

Be Agileに向けて確信と勇気をくれる一冊

私が所属しているチームでもウォーターフォールが採用されていますが、本書で紹介されているチケット管理やslack、KPTやFun Done Learnなどの振り返り手法は既に実践していました。
そのため、「アジャイルな手法とは何か」というより「アジャイルな手法の何が嬉しいのか」を知りたくて本書を手に取りました。

読んでみてまず、「今いる環境はめちゃくちゃありがたいんだな」と思いました。
ラクティスの導入前後の物語や、具体的なノウハウについての解説を読み進めていくと「確かにこの手法を通じてこういうメリットを享受しているな」と思う部分が沢山あり、
章を追ってどんどん良くなっていく運用チームの様子を見て、
こういう過程を経て良くなっていった結果今のチームがあって、そんなチームで働かせて貰えているんだ
と、運用チームへの思い入れが強くなっていくごとに、自分の環境のありがたさ、チームの先輩方の偉大さを強く実感しました。

また、チームのセイチョウ過程を追うことで、Be Agileとはどういうことなのかを具体的に知ることができました。
8章以降ずっと胸熱展開が続くのですが、特に印象的だったのは、エピローグの相良 真希乃の下記の一節でした。

「大切なのは、自分たちの問題や課題を言語化すること、周りの仲間たちと問題意識の景色を合わせること、そしてそれを仕組みや仕掛けで解決することなのだ。その解決方法は、ウォーターフォールの世界のやり方だろうが、アジャイルの世界のやり方だろうが、どちらでもいい。

まさにこの姿勢こそがBe Agileの体現だなと感じました。どんな手法を実践しているかでプロダクトの価値が決まる訳ではなく、"本当に価値のあること・あるべき姿"のために行動を起こすことが重要で、アジャイルの手法はその助けとなる。
この相良 真希乃の言葉は、一つの羅針盤として、今の自分を奮い立たせてくれると共に、今後歩みを進めていくうえで迷い悩むことがあっても勇気づけてくれると確信しました。


「今所属しているチームの開発プロセスが、どんな意図で出来上がったのか」を知りたくて本書を手に取りましたが、本当に出会えて良かったと思える一冊でした。
自分もチームのメンバーとして良い文化を作っていけるように、また明日から頑張ります。

www.shoeisha.co.jp

「分からないから聞く」から「分かるようになるために聞く」へのスイッチング

何回かリリースを経験した中で、1リリースという単位で「何をするか」と同じように「何を学ぶか」をハッキリさせることが結構大事なのではないか、と思ったのでまとめてみます。


機能実現には自信を持てるが、価値実現にはあまり自信を持てない開発チーム

開発の流れをざっくり説明すると、市場からの改善要望や既知の市場障害に応じて要件が発生しており、期初にそれらの幾つかが棚卸されてリリース単位のロードマップが引かれます。その後、細分化されたプロジェクトによってウォーターフォール的/アジャイル的に開発が進んでいきます。

ロードマップ実現のために要件定義~設計~実装と進めていくと「この仕様は要望を満たすのか」が分からなくなる場面があります。その度に、検証側を含めた開発チームのメンバーで議論したり、企画やテクニカルサポートなど顧客と接しているメンバー(以降、フロントと表記)に相談したりします。
開発チーム内だけで議論するかフロントも巻き込むかの判断は、「開発チームとしての判断のしやすさ」によるのかなと思うのですが、どのケースでも「フロントの意見を聞けるなら聞けるほうがマシ」という意識がありました。
これには幾つか原因があると思うのですが、主な原因は

  • 「お客様が機能をどういう風に使ってどういう部分で困りやすいか」の判断材料が開発チームにないこと
  • 「機能のこの部分を充実させると親切だろうけど、どの程度ビジネスとして価値があるのか」の判断能力が弱いこと

だと思います。

この原因に対処するため、今の開発チームは"チーム内でどう判断すればいいか分からないから意見を欲しい"とフロントに相談したりしなかったりしています。

「分からないから聞く」で残るもの

現状のプロセスでは上記の通り「分からないから聞く」を行っています。「分からないから聞く」を行うと、「聞いたから分かる」となります。ここで「分か」ったことは「分からなかったこと」のみです。

何を言いたいのかと言うと、具体的なケースについて「仕様と要望の一致度」を聞いて明らかにするだけでは、次のリリースで別の要件を対応するときにまた同じことを繰り返してしまうということです。

「分からないから聞く」では、未来の「分からないから聞く」が残ってしまうのです。

その場限りの解決から根本解決へのスイッチング

今回の「分からないから聞く」の目的は、「この仕様は要望を満たすのか」を判断することでした。
言い換えると、"開発チームだけで判断できない状態"から"(フロントの力を借りて)判断できる状態"になることが目的でした。

目的をこのように整理すると、"判断できる状態になる"ために他にできることがないか?と考えることができます。
その上でこれまでの開発プロセスの延長で小さく試せることを考えると、「分からないから聞く」に加えて"今よりちょっとだけ判断できる状態になる"ために何かを得ることはできないか?という発想が浮かびます。

そうすると、「分からないから聞く」という事象解決のための行為を「分かるようになるために聞く」という事象解決+学びを得に行く行為へと発展させることができます。

このような姿勢を持てば、同じ聞くという行為の中でも「今回のようなフロントの知見をどうすれば開発チームも取り込めるか」と踏み込んだ質問も出てくるようになって開発とフロントとで歩み寄ることができます。また、「そういう課題は確かにあるから、じゃあこういうタイミングで相談するようにしよう」と現場レベルでのコラボレーションも実現されるようになります。

「何を学ぶか」を明確にする

ある程度開発が続いてきたプロダクトの開発プロセスには、大なり小なり改善すべき問題が生じていると思います。全ての問題を解消する一手など存在しないので、開発スピードを保ったまま改善していくしかありません。
改善のために「xxxxをしよう」というとき、それは"理想の状態"に効率的に向かうための行動提案・様式提案となっているはずです。しかしそれだけだと、行動・様式に沿った結果"理想の状態"に近づくことはできても、同じ行動をずっと繰り返さなくてはいけないかもしれません。

そうではなく、「今その行動をとる目的は何で、じゃあその目的の達成のために何を積み上げていけるか」という「自分たちのレベルを上げるために何を学ぶか」思考で行動提案・様式提案に学びの口を一つでも加えることによって、よりチームにフィットする行動や後に残る学びを得やすくなるのではないでしょうか。


リリース単位で「何をするか」という行動ベースの目標や「どういう状態を目指すか」という状態ベースの目標を立てるのと同時に、「何を学ぶか」という姿勢ベースの目標を立ててみると良いのではないかと漠然と思ったので、考えを整理してみました。

ちょうど次のリリースに向けてまた動き出しているタイミングなので、色々と試しながら次の開発期間を学び多いものにできるよう頑張りたいです。

2021年の振り返り<1月・2月>

年初、2021年の上半期の目標を立ててみました。 2か月経ったので、振り返ってみます。

キャリア観の変化

Q4では上長やチームメンバーに自分の考えていることをどんどん発信していくことを意識的に行い、自分で考えるだけではなく相談しながらリアクションを貰いながらチームで物事を進めていくという姿勢を取れるようになってきました。今のところ良い方向に自分を動かせているのかなと思います。
そんな中で、年初ではふわふわしていたキャリア観が定まってきました。

キャリアの重心をQAへ

Q4の初めに上長と1on1を行い、チームの掲げる目標に対してAndroid開発を軸に貢献するかソフトウェアテストを軸に貢献するかの相談をさせていただきました。いざ相談してみると、自分が考えていたチームの弱み・改善できるスポットと上長の考えていたがある程度一致しており、じゃあそこに力を入れていこうということになりました。
自分がそのとき感じていた課題は、テスト計画が後から振り返りしやすいものとなっていなかったり、上流工程から検証チームが参加するものの品質担保のための観点がメンバーに浸透していなかったり、といったものでした。
そのため、手段としてどのロールを取るか、という話になったとき必然的にQAが浮かびました。

その後も1on1や別の機会を設けていただき、前回のブログでも書いた通り、今はQAチームを立ち上げようというところに全力を注いでいます。

来期以降、少なくとも2年間はキャリアをQAに全振りしようと考えています。

直近の課題

プロダクト開発全体に寄与するためのソフトウェアテスト・品質保証に関する知識が圧倒的に足りず、ソフトウェアテスト技法ドリルやSQuBOKなどを中心にもっともっと足腰を鍛えなきゃいけないと感じています。

進捗

肝心の、年初に立てていた目標の進捗はと言いますと…。

web

  • Real World HTTP 第2版www.oreilly.co.jp
    • まだ買ってもいないです。
  • JavaScript Primerjsprimer.net
    • 第1部のプロトタイプオブジェクトまでと、第2部のAjax通信とCLIアプリの部分を勉強しました。動的型付けにようやく慣れてきたかなというところです。

インフラ

  • イラストでわかる DockerとKubernetesgihyo.jp
    • まだ買ってもいないです。
  • コスパのいいシステムの作り方gihyo.jp
    • 3章の「開発費を削減するための工夫」まで読んで、25%くらいの進捗です。プロダクト開発というよりはプロジェクトの視点を学べて、業務をする上で持っておきたい視点を一つ増やせているのは良いなと捉えています。

android

  • Androidテスト全書peaks.cc
    • 2章まで終わり、モックとかスタブとかスパイの概念の使い分けを認識しました。SETのスキルを強化していくなら必須の知識と思いますので、引き続き取り組んでいきたいです。

QA

  • JSTQB FLjstqb.jp
    • 受験しました。合格できたらいいな。。
  • SQuBOK Guide V3www.ohmsha.co.jp
    • 1章を最低限読み終えて、2章を読み進めています。いざ本腰入れて読んでみたら読み物としても面白く、QAとしての視野が広がっている実感もあるので、引き続き頑張りたいです。

エンジニアリング

  • 事業をエンジニアリングする技術者たちtechlog.voyagegroup.com
    • 先輩に借りていた本、緊急事態宣言のタイミングで返却しました。自分にはまだ早かったので、半年後くらいに買ってまた読もうと思います。
  • ここはウォーターフォール市、アジャイルwww.shoeisha.co.jp
    • 数少ない出社日の電車の中でゆっくり読み進めています。チームで物事に取り組むためのヒントが沢山あるので、早いところ読み切りたいです。

次の2か月のアクション

キャリアの中心にQAを据える決意を固めたため、SQuBOKを中心にソフトウェアテスト・品質保証の知識をもっともっと付けていこうと思います。
キャリアはQAに全振りするとはいえ、いつでも全方向に飛び移れるようにWebやクライアント開発などの勉強も細く長く続けていこうと思います。なので、年初に立てた最低限の目標は変えず、頑張ります。

ダメだったら下期の取り組み方を変えれば良いのかなと思うので、特に細かい数値目標は立てずに、実直に取り組んでいきます。

ソフトウェアテストジャーニーへの船出 (JSTQB FLを受験したハナシ)

気付けば2021年も1か月と半分が過ぎ、本日はJSTQB FLの受験日でした。

受けた直後の今は、試験の手ごたえがどうとかより、やっと一区切りついたという感想が強いです。
本記事では、新卒1年目のひよこエンジニアである自分がなぜ試験を受けたのか、今後どうしていきたいのか、などをつらつら書いてみようと思います。

JSTQBとは何か、については公式のHPをご覧ください。jstqb.jp

ソフトウェアテストとの出会い

僕は2020年4月に、あるソフトウェア開発会社に新卒入社しました。なので1年前は大学生でしたし、それもプログラミングの経験が多少ある、くらいの平凡な学生でした。

入社後はAndroid開発チームに配属され、社会人生活もチーム開発もAndroid開発も初めて、という状況の中で「教わったことを覚えていく」のに必死、という日々を過ごしていました。
6月の半ばまでAndroidの開発を行い、VIPERアーキテクチャ疎結合コンポーネントを組み、コンポーネント同士のテストコードもしっかり書いていく、という経験をすることができました。
この時点でソフトウェアテスト自体に触れてはいたのですが、より強くテストを意識するようになったのは、そのあとの工程であるシステムテストに参加する機会をいただいたときでした。

僕が所属している開発チームのシステムテストは、いわゆるテスト担当者がテストの観点を作成し、観点に基づいてテストを実装し、そして実行していくというベーシックな流れを採用しています。
最初の機会ではテストケースが既に用意されていたので、テスト実行を担当しました。
最初は書かれている通りにテストを実行して期待値と比較して…とやっていたのですが、テスト手順や期待値への疑問点をテスト作成者に質問したり、テストで見つかったバグへの対応についてマネージャーから指摘をいただいたりする中で、テストケースの効率的な組み方であったり、そもそもテストを実装する前段階で議論するべきことがあるなと思ったり、プロダクト開発におけるソフトウェアテスト、という命題についてよくよく考えるようになっていきました。

今思えば、テスト工程での反省を開発プロセスの反省、と捉えられたのは、Android開発者として実装タスクを行っていたからなのかもしれないですね。
自分の実装したものをテスト担当者としてテストし、その中でソフトウェアテストと向き合っていった。
これが僕のソフトウェアテストとの出会いでした。

JSTQBとの出会い

6月からの3ヶ月をテストとバグ修正に捧げる中で、ソフトウェアテストそのものを体系的に勉強する必要性を感じていました。
そんな中、あまり深く考えずに"ソフトウェアテスト 資格試験"で検索してみたとき、JSTQBの存在を知りました。

ホームページを見て、今の自分にぴったりで良さそう!と思い、
シラバスを見て、凄すぎる!!これをまさに勉強するべき!!と思い、
FL試験の開催日程を見て、次の2月!丁度良すぎる!!!と思いました。

テス友アプリがあったのも、とてもありがたかったです。

テスト実行を担当する中でソフトウェアテストを体系的に勉強したいと思い、何となく資格試験を調べてみたらJSTQBの存在を知った。
これが僕のJSTQBとの出会いでした。

そうこうしているうちに、無事10月にメジャーバージョンアップリリースを迎えることができました。

テスト担当としての日々、飛躍

ソフトウェアテストの勉強を熱心にしていることを評価され、次のメジャーバージョンアップリリース向けの要件には、テスト主担当として参加することになりました。

2月現在、これからリリースを迎えるフェーズなのですが、今日までに実例マッピングを実践してみたり、テスト設計時に品質特性との対応を整理してみたりしていました。もちろんテスト設計〜実装までも一丁前に頑張っています。

勉強したことを開発プロセスに取り入れていく日々の中、僕自身の志向が「品質改善にミッションを持ったロール=QAチームを作りたい」 というチームの状況ともリンクし、今は要件対応に加えて、新たにQAチームを立ち上げようというところに参画させていただいています。

FL試験の受験、そしてソフトウェアテストジャーニーへの船出

今回のJSTQB FL試験の受験は、
QAを立ち上げるにあたり、品質目標をどうするべきか、テスト戦略はどうあるべきか、という議論を直近でしている中での受験となりました。
そのため、個人的には

社会人1年目の集大成 且つ 来期以降の大きな前進への第一歩
=>
ソフトウェアテストジャーニーへの船出

という意識を強く持って挑んだJSTQB FL試験でした。

シラバスを見ていただくとよく分かるのですが、JSTQB FLの試験範囲はまさにソフトウェアテスト全般であり、この領域でプロダクト開発に貢献していくためには通らざるを得ない登竜門といっても過言ではないです。

ソフトウェアテストとは何か、その意義の解説から、テストプロセスの仔細について、また具体的なテスト技法についても勉強できるので、
ソフトウェアテストを真剣にやっていきたい人には本当におすすめしたいです。


今後の展望については、上述した通りこれからQAチームを立ち上げていき、今接しているプロダクトの品質を未来にわたって改善していくべく、ソフトウェアテストを武器に日々邁進していこうと考えています。

JSTQB FL試験が終わってまずはひと段落というところですが、ソフトウェアテストジャーニーは始まったばかりですので、FLシラバスで学んだことを糧に、さらにソフトウェアテストの勉強をしていこうと思います。
具体的には、SQuBoKに齧り付いていったり、テスト自動化についてもっと勉強したり、さらにスキルを伸ばしていきたいです。また、Androidやwebの開発についても勉強して、実装者とうまくコミュニケーションを取れるQAエンジニアになっていきたいです。

受かれば良いな…と思うのですが、もしFL試験に落ちていたら、また勉強し直して再挑戦したいと思います。

2021年(1月~6月)の目標

目標到達度という観点で2021年の日々の振り返りをしやすくするため、一つの記事にまとめてみます。

1月~6月にかけて読みたい本・サイト

web

インフラ

  • イラストでわかる DockerとKubernetesgihyo.jp
  • コスパのいいシステムの作り方gihyo.jp
    • "機能を実現すること"で手一杯で、あらゆる面でコスパという観点が抜けていることを2020年度Q3の中で痛感しました。DevOpsにおけるコスパ、という視点を習得したいです。

android

  • Androidテスト全書peaks.cc
    • 昨年の配属当初、VIPERアーキテクチャを用いてアプリケーション開発を行ったため、開発と並走してテストも書くという行為は実践できています。しかし、Android自体の理解が浅くテストにおけるベストプラクティスをまだ知りません。"テスト"と銘打っているこの書籍を通して、テストを書けるAndroidエンジニアとして一歩進みたいです。下記の記事も、この本を読もうというきっかけになりました。

QA

  • JSTQB FLjstqb.jp
    • 読みたいというより、JSTQBのFL試験を2月13日に受験します。テス友アプリも使ってしっかり合格したいです。
  • SQuBOK Guide V3www.ohmsha.co.jp
    • 上半期で目を通せるとは到底思えませんが、ストレッチ目標として掲げます。用語や方法論を一つ一つ追っていったらキリがないので、まずは読み切ることをゴールとします。

エンジニアリング

  • 事業をエンジニアリングする技術者たちtechlog.voyagegroup.com
    • 先月から先輩に借りている本。新米過ぎて雰囲気でしか理解できない部分もありますが、読むと場数を稼げるような感覚でいます。これも読み切ることをゴールとして早く返却したいです。(先輩すいません、、、)
  • ここはウォーターフォール市、アジャイルwww.shoeisha.co.jp
    • 所属しているチームは10年選手のプロダクトを抱える、いわゆる成熟したチームです。ので、この本に書かれていることは大なり小なり実践されているように思えますが、今やっていることにどういう意味・メリットがあるのかを改めて理解したいです。

ピックアップしやすいので本やサイトを挙げましたが、日々Androidのデベロッパーガイドをチェックしたり、色んなテックブログを見る癖は継続して意識していきたいです。また、個人開発の中で学んだことや業務で行っていることも、このブログや弊社のテックブログで発信していきたいです。

2021年さらに飛躍して、携わるプロダクトの質の向上に努めていきたいです。

dockerに入門してみる(その9)(終)

前回の記事はこちら↓

ren-opdev.hatenablog.com

今回は"dockerに入門してみる"シリーズの最終回です。最後は、コンテナイメージをビルドする上でのベストプラクティスを見ていきます。

イメージをスキャンする

事前に見つけられる脆弱性があるならば、見つけて潰してしまいたいですよね。

DockerはSnykとパートナーになっているため、docker scanによってコンテナイメージの脆弱性をスキャンすることができます。
試しにtutorialを動かしているgetting-startedをスキャンしてみます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker scan getting-started
Docker Scan relies upon access to Snyk, a third party provider, do you consent to proceed using Snyk? (y/N)
y

Testing getting-started...

✗ Medium severity vulnerability found in openssl/libcrypto1.1
  Description: NULL Pointer Dereference
  Info: https://snyk.io/vuln/SNYK-ALPINE311-OPENSSL-1051931
  Introduced through: openssl/libcrypto1.1@1.1.1g-r0, openssl/libssl1.1@1.1.1g-r0, apk-tools/apk-tools@2.10.5-r0, libtls-standalone/libtls-standalone@2.9.1-r0
  From: openssl/libcrypto1.1@1.1.1g-r0
  From: openssl/libssl1.1@1.1.1g-r0 > openssl/libcrypto1.1@1.1.1g-r0
  From: apk-tools/apk-tools@2.10.5-r0 > openssl/libcrypto1.1@1.1.1g-r0
  and 4 more...
  Fixed in: 1.1.1i-r0

✗ Medium severity vulnerability found in musl/musl
  Description: Out-of-bounds Write
  Info: https://snyk.io/vuln/SNYK-ALPINE311-MUSL-1042763
  Introduced through: musl/musl@1.1.24-r2, busybox/busybox@1.31.1-r9, alpine-baselayout/alpine-baselayout@3.2.0-r3, openssl/libcrypto1.1@1.1.1g-r0, openssl/libssl1.1@1.1.1g-r0, zlib/zlib@1.2.11-r3, apk-tools/apk-tools@2.10.5-r0, libtls-standalone/libtls-standalone@2.9.1-r0, busybox/ssl_client@1.31.1-r9, gcc/libgcc@9.3.0-r0, musl/musl-utils@1.1.24-r2, pax-utils/scanelf@1.2.4-r0, libc-dev/libc-utils@0.7.2-r0
  From: musl/musl@1.1.24-r2
  From: busybox/busybox@1.31.1-r9 > musl/musl@1.1.24-r2
  From: alpine-baselayout/alpine-baselayout@3.2.0-r3 > musl/musl@1.1.24-r2
  and 12 more...
  Fixed in: 1.1.24-r3



Organization:      undefined
Package manager:   apk
Project name:      docker-image|getting-started
Docker image:      getting-started
Platform:          linux/amd64

Tested 16 dependencies for known vulnerabilities, found 2 vulnerabilities.

For more free scans that keep your images secure, sign up to Snyk at https://dockr.ly/3ePqVcp

スキャン結果として、見つかった脆弱性の種類やどのバージョンのライブラリで修正されているかを吐き出してくれます。docker scanのオプションについては、下記の公式に詳しく載っています。 docs.docker.com コマンドラインからイメージをスキャンするほか、Docker Hubごとスキャンすることも出来るようです。 docs.docker.com

イメージ構成を見る

docker image historyコマンドを用いると、そのイメージがどのレイヤーで構成されているかを見ることができます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker image history getting-started
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
7d2a760d3274   13 days ago    CMD ["node" "src/index.js"]                     0B        buildkit.dockerfile.v0
<missing>      13 days ago    RUN /bin/sh -c yarn install --production # b…   85.2MB    buildkit.dockerfile.v0
<missing>      13 days ago    COPY . . # buildkit                             4.62MB    buildkit.dockerfile.v0
<missing>      2 weeks ago    WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      5 weeks ago    /bin/sh -c #(nop)  CMD ["node"]                 0B
<missing>      5 weeks ago    /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B
<missing>      5 weeks ago    /bin/sh -c #(nop) COPY file:238737301d473041…   116B
<missing>      5 weeks ago    /bin/sh -c apk add --no-cache --virtual .bui…   7.62MB
<missing>      5 weeks ago    /bin/sh -c #(nop)  ENV YARN_VERSION=1.22.5      0B
<missing>      5 weeks ago    /bin/sh -c addgroup -g 1000 node     && addu…   76.5MB
<missing>      5 weeks ago    /bin/sh -c #(nop)  ENV NODE_VERSION=12.20.0     0B
<missing>      8 months ago   /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      8 months ago   /bin/sh -c #(nop) ADD file:b91adb67b670d3a6f…   5.61MB

出力された各行がレイヤーを表しています。手軽にレイヤーのサイズを見ることができるので、どのレイヤーが肥大化しているかをパッと判別することができます。

レイヤーキャッシュを用いてビルド回数を減らす

Dockerfileの書き方を工夫することで、ビルド回数を減らすことができます。まずは、以前のチュートリアルで触ったDockerfileを引用します。

FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

docker image historyで見た通り、コマンドそれぞれは一つずつレイヤーになっています。また、イメージに変更を加えるとyarnの依存関係を再インストールする必要があります。
ビルドするたびに同じ依存関係をインストールし直していくのは非効率なので、上手いこと依存関係をキャッシュして解決していきます。

Nodeベースのアプリケーションでは、アプリケーションの依存関係は全てpackage.jsonに記述されます。そのため、まずpackage.jsonをコピーして依存関係をインストールしてしまい、その後に他のソースコードをコピーすれば余計な再インストールを避けられます。

具体的には、以下のようにDockerfileを書きかえます。

FROM node:12-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]

次に、Dockerfileと同じ階層に.dockerignoreファイルを作成します。

node_modules

.dockerignoreはDocker(正確にはビルド時に動作するdaemon)に無視してほしいファイルを指定するためのファイルで、今回node_modulesはRUNコマンドを実行した際に上書きされるため無視する対象とします。

では、一度イメージをビルドしてみます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker build -t getting-started .
[+] Building 13.9s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.1s
 => => transferring dockerfile: 175B                                                                               0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 52B                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:12-alpine                                                  0.0s
 => [1/5] FROM docker.io/library/node:12-alpine                                                                    0.2s
 => => resolve docker.io/library/node:12-alpine                                                                    0.0s
 => [internal] load build context                                                                                  0.1s
 => => transferring context: 8.65kB                                                                                0.0s
 => [2/5] WORKDIR /app                                                                                             0.0s
 => [3/5] COPY package.json yarn.lock ./                                                                           0.1s
 => [4/5] RUN yarn install --production                                                                           12.1s
 => [5/5] COPY . .                                                                                                 0.1s
 => exporting to image                                                                                             1.3s
 => => exporting layers                                                                                            1.3s
 => => writing image sha256:d71dd886d28378420a1fb3f6782c4d9816f52e4761d08ecca02ca3c893818ebc                       0.0s
 => => naming to docker.io/library/getting-started

Dockerfileの記述通りに処理が進んでいますね。
では次に、src/static/index.htmlを適当にいじってみて再度ビルドしてみます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker build -t getting-started .
[+] Building 0.3s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 32B                                                                                0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 34B                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:12-alpine                                                  0.0s
 => [1/5] FROM docker.io/library/node:12-alpine                                                                    0.0s
 => [internal] load build context                                                                                  0.0s
 => => transferring context: 3.43kB                                                                                0.0s
 => CACHED [2/5] WORKDIR /app                                                                                      0.0s
 => CACHED [3/5] COPY package.json yarn.lock ./                                                                    0.0s
 => CACHED [4/5] RUN yarn install --production                                                                     0.0s
 => [5/5] COPY . .                                                                                                 0.1s
 => exporting to image                                                                                             0.1s
 => => exporting layers                                                                                            0.1s
 => => writing image sha256:2807d4c8a4f026a88ac4005c791d060de6a8a23ae1275046047b1b08499688c3                       0.0s
 => => naming to docker.io/library/getting-started

途中CACHEDとある通り、イメージのキャッシュが上手く働いて余計なインストールを省けたことが分かります。これで、より効率的なビルド作業が実現できますね!

マルチステージビルド

ビルドするだけのステージと、成果物を載せるだけのステージ…と複数のステージを使い分けていくことで、最終的なイメージのサイズを小さくすることができます。

Maven/Tomcatの例

Javaベースのアプリケーションをビルドするには、コンパイル時にJDKが必要となります。しかし、JDK自体はプロダクション環境には必要ないものです。MavenやGradleを使用する際も同様ですね。こういう場合に、マルチステージビルドが有用となります。

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

この例では、buildステージでMavenを使用したJavaアプリケーションのビルドを行い、tomcatステージでbuildステージからビルド成果物をコピーしています。

Reactの例

Reactアプリケーションをビルドするには、Node環境が必要となります。しかし、サーバーサイドで画面の描画を行わない限りはプロダクションビルドにNode環境は必要ありません。こういう場合にも、マルチステージビルドが有用です。

FROM node:12 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

node:12イメージを用いてビルドし、成果物をnginxコンテナにコピーするだけ…。Nodeまわりのことはよく分かりませんが、確かに効率的っぽいですね。


コンテナイメージのビルドをより速く、より効率的に行う方法を(少し)知ったところで、このチャプターは終わりです。そして、これにてDocker 101 Tutorialも終了です!

Dockerの世界はまだまだ広く、一口にDockerといってもコンテナオーケストレーションCNCFなど様々な領域があります。 landscape.cncf.io 会社ではKubernetesを利用しているため、次は話題のイラストでわかるDockerとKubernetesを読んで更にDocker自体の理解やKubernetesへの理解を深めたいです。

とりあえずは、このチュートリアルを年内に終えられてよかったです。
よいお年をお迎えください!

dockerに入門してみる(その8)

晦日。年内に101 Tutorialを終えられるのでしょうか。

前回の記事はこちら↓ ren-opdev.hatenablog.com

今回は、Docker Composeを利用してApplication Stackを操作していきます。
まずDocker Composeとは

マルチコンテナのアプリケーションを共有したり、定義したりするために使えるツールのこと。
YAMLファイルでサービスを定義することができ、シングルコマンドで立ち上げ・取り壊しが可能である。

Composeを使うメリットは、

  • Application Stackを一つのファイルの中で定義できること
  • そのファイルをリポジトリのルートに置いておけること
  • そうすることで、他の人が開発に参加しやすくなること

です。リポジトリをクローンしてComposeするだけでApplication Stackを整えられる、という点が便利なのですね。

Composeファイルを作成する。

Docker Desktopを利用しているので、既にDocker Composeはインストール済みです。docker-compose versionを実行すると、Composeのバージョンを見ることができます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker-compose version
docker-compose version 1.27.4, build 40524192
docker-py version: 4.3.1
CPython version: 3.7.4
OpenSSL version: OpenSSL 1.1.1c  28 May 2019

では実際にComposeファイルを作っていきます。まずはリポジトリのルートにdocker-compose.ymlを作成します。
作成したファイルの先頭で、version:スキーマバージョンを定義します。Compose file referenceより、現在の最新バージョンは3.8のようです。

version: "3.8"

次に、services:で実行対象のコンテナリストを定義します。

アプリケーションサービスを定義する

最初にアプリケーション部を定義していきます。前回コンテナ立ち上げに使ったコマンドは以下の通りです。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker run -dp 3000:3000 `
>>   -w /app -v "$(pwd):/app" `
>>   --network todo-app `
>>   -e MYSQL_HOST=mysql_v2 `
>>   -e MYSQL_USER=root `
>>   -e MYSQL_PASSWORD=secret `
>>   -e MYSQL_DB=todos_ja `
>>   node:12-alpine `
>>   sh -c "yarn install && yarn run dev"

これをyamlファイルに書き下していくと、以下の通りになります。

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql_v2
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos_ja

比較してみると、どこがどう対応しているかが分かりますが、portとvolumeのマッピング記法が少し特殊ですね。また、image: node:12-alpinenodeは、自動的にネットワークエイリアスとして登録されるようです。

MySQLサービスを定義する

同様に、MySQL部を定義していきます。前回コンテナ立ち上げに使ったコマンドは以下の通りです。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker run -d `
>> --network todo-app --network-alias mysql_v2 `
>> -e MYSQL_ROOT_PASSWORD=secret `
>> -e MYSQL_DATABASE=todos_ja `
>> mysql:5.7 `
>> --character-set-server=utf8mb4 `
>> --collation-server=utf8mb4_unicode_ci

これをyamlファイルに書き下していくと、以下の通りになります。

version: "3.8"
services:
  app:
    # appサービスの定義
  mysql:
    image: mysql_v2:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD=secret
      MYSQL_DATABASE=todos_ja
      
  volumes:
    todo-mysql-data:

ここでの注意点は、docker runのときは自動的に作成された名前付きボリュームを明示的に定義するという点です。それに伴い、MySQLの定義内のvolume:に対応するvolume定義を、MySQL定義の下に加えています。

Application Stackを実行する

では、Composeを利用してApplication Stackを実行しましょう。docker-compose upコマンドで起動できます。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker-compose up -d
ERROR: The Compose file '.\docker-compose.yml' is invalid because:
services.mysql.environment contains an invalid type, it should be an object, or an array
Unsupported config option for services.volumes: 'todo-mysql-data'

失敗しました。そしてチュートリアルにも書いてない…。大人しくエラーログを読んでみると

  • mysql配下のenvironmentがおかしい
  • volumesのconfigオプションをサポートしていない

と言われています。environmentについては、appサービスは通ってるのにmysqlは通っていない。。
と思い見比べてみたら、記法を間違えていました。正しくは以下の通りです。

    environment:
-      MYSQL_ROOT_PASSWORD=secret
-      MYSQL_DATABASE=todos_ja

    environment:
+      MYSQL_ROOT_PASSWORD: secret
+      MYSQL_DATABASE: todos_ja

2つ目のエラーはよく分からなかったのでDocker Forumsで検索しました。

forums.docker.com

インデントがおかしい…?
改めてチュートリアルの例文と比較してみると、確かにインデントを余分に加えてしまっていました。

-  volumes:
-    todo-mysql-data:

+volumes:
+  todo-mysql-data:

では、気を取り直してコマンドを実行します。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker-compose up -d
Creating network "app_default" with the default driver
Creating volume "app_todo-mysql-data" with default driver
Pulling mysql (mysql_v2:5.7)...
ERROR: The image for the service you're trying to recreate has been removed. If you continue, volume data could be lost. Consider backing up your data before continuing.

Continue with the new image? [yN]y
Pulling mysql (mysql_v2:5.7)...
ERROR: pull access denied for mysql_v2, repository does not exist or may require 'docker login': denied: requested access to the resource is denied

失敗しました。「mysql_v2へのpullアクセスが拒否された」らしいです。
MySQLのイメージを使おうとしていたのにimage: mysql_v2:5.7でイメージの名前を異なるものにしていたのが原因のようです。正しくはimage: mysql:5.7ですね。イメージ名が自動的にネットワークエイリアスになるだけで、ここに任意のネットワークエイリアスを入れていい訳ではなかったですね…。

改めてコマンドを実行します。

PS C:\Users\530lo\Documents\docker\tutorial\app\app> docker-compose up -d
Creating app_mysql_1 ... done       
Creating app_app_1   ... done                 

成功しました!
docker-compose logs -fでログを確かめると、app_1サービスが起動し、mysql_1サービスも紆余曲折を経て起動したことが分かりました。

ダッシュボードを確認してみると、リポジトリ名=appでApplication Stackがまとめられていることが分かります。 f:id:renkataoka:20201231005629p:plain コンテナごとにサービス名などが記載されているため、どのコンテナが何に対応しているかが一目で分かりますね。

Application Stackを停止する

Composeによって起動したApplication Stackを停止するには、docker-compose downコマンドを実行すればよいです。このとき、デフォルトではボリュームをは削除されないため、削除したい場合は--volumesフラグを付けると良いそうです。


Docker ComposeによるApplication Stackの操作方法が分かったところで、このチャプターは終わりです。

次のチャプターでは、コンテナイメージをビルドする際のベストプラクティスを勉強します。