オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

1ヶ月以上 在宅勤務を続けた弊社エンジニアのアンケート結果を発表します!

オールアバウトエンジニアの在宅勤務アンケート結果発表

こんにちは!オールアバウトでデータエンジニアをしている@ondaljhです。

新型コロナウィルスの影響で在宅勤務を実施している企業も多いと思います。オールアバウトグループも3月初めから在宅勤務を実施したので、実施から1か月以上になりました。
今回はオールアバウトのエンジニア達に在宅勤務による業務環境変化等をアンケートしたのでその結果を発表させていただきます!

業務関連

1.在宅勤務になってから打ち合わせの数はどう変わりましたか?

f:id:allabout-techblog:20200422162741p:plain

2.自身が開催する打ち合わせの場合、使用するツールはslackとzoom、どちらをより多く使用しますか?

f:id:allabout-techblog:20200423151351p:plain

その他を選んでいただいた方の中では下記のような意見もありましたー

  • 社外の打ち合わせはZoom、それ以外はHangout使っています

2-1.質問2で選んでいただいた項目について、その理由を教えてください

※回答からいくつか抜粋

  • Slack派

    • slackの方が常に見ているのでそっちの方が開きやすいため
    • 少人数の打ち合わせが多いため、わざわざZoomで部屋を作る必要性を感じないため。また、Zoomはセキュリティが心配&Slackのほうが画面共有時の書き込みが自由にできるので。
    • Zoomだと40分の制限があるため
  • Zoom派

    • 背景を変更できるから
    • slackよりもzoomの方が軽い気がするから
  • どっちでも良い派

    • 30分以内、3名ぐらいまではSlack。3名以上30分以上ならZoomでやっています
    • MTGによって定着している、選び直すなら安定性を感じるZoom
    • どっちでもよいので気分次第。
  • その他

    • Slackは参加者固定の打ち合わせなら会議作るのが楽だけど、定例等のスケジュールを自動化できないのがつらいため。カレンダーベースでスケジュールできるHangoutが比較的楽だと思いました。

3.割り込み作業の数はどう変わりましたか?

例えば、急な問い合わせや調査依頼、予定されていなかった打ち合わせなど、当日のスケジュールに含まれていなかった作業の増減について教えてください

f:id:allabout-techblog:20200422163008p:plain

3-1. 質問3の傾向についてご意見又は感想を聞かせてください

※回答からいくつか抜粋

  • 減った
    • うれしい。
    • 口頭で質問しにくる人が減ったから。また、ある程度情報をまとめてきてから相談してくれるので、以前よりも進めやすい
    • 通話をつなぎながらの作業が増えたので、割り込みで中断するというケースが少なくなりました。個人的には集中できて良いと思っています。
  • 変わらない
    • 業務のボリュームは変わらず。問い合わせが多いです。
    • 自分の業務的には問い合わせ等はオフライン・オンライン関係ないのが多いから
  • 増えた
    • たぶん、増えたのは、在宅だからではなくコロナ対応のせいです(コーポレートサイト担当なので、コロナ関連のお知らせで、突発サポート業務が増えています)。

4.気軽な相談の数はどう変わりましたか?

f:id:allabout-techblog:20200422163044p:plain

4-1. 質問4の傾向についてご意見又は感想を聞かせてください

※回答からいくつか抜粋

  • 減った
    • 気軽に聞けないのでまずは自分で調べるようになった
    • 口頭で質問しにくる人が減ったから。また、ある程度情報をまとめてきてから相談してくれるので、以前よりも進めやすい
    • ただの雑談や通りすがりの雑談が出来ないのは長期的にはマイナスになる。
    • 相談なしで進んでしまうと手戻りが増えやすいので、極力減らさないようにしたいと思っています。が、相手の方がオフラインだと「今日はお休みなのかな?」と思ってしまい、なかなか気軽には声をかけにくいです。
  • 変わらない
    • 相談する側であることが多いのですが、Slackのテキストベースで基本的にはやりとりするので、言語化のクオリティがお互いに上がりディスコミュニケーションが減ったような気がしています。
    • slackで気軽に連絡が取れる
    • 以前と変わらない感じで気軽に相談をするようにしているので、その辺りのハードルは高くない
  • 増えた
    • DMでのこれどうしたらいい?が増えました

5.業務への集中度の変化はありましたか?

f:id:allabout-techblog:20200422163108p:plain

5-1. 質問5の傾向についてご意見又は感想を聞かせてください

※回答からいくつか抜粋

  • 集中しやすくなった側
    • モブプロ中は余計なものが入り込まなくなって集中しやすくなった
    • マイペースで働けるようになったからある意味集中度もあがったと思う
  • 変わらない
    • 最初は個人作業は集中しやすかったのですが、その分カレンダーやSlackの通知に敏感になり、結果総合的に見ると在宅じゃないときとあまり変わらないと感じました
    • 特に在宅だからといって変わりはない
    • 特に変わらない。気軽に気分転換にお散歩に行けるようになったぐらい
    • 業務後もすぐ業務に戻れるため、公私の区別が付かなくなりがち。結果、緊張感が間延びしつつある。ただ、まとまった時間が取れるのは非常に助かる。
  • 集中し辛くなった側
    • 環境が整っていなかったので、仕事をする際の姿勢による負荷などであまり集中できていなかった
    • 自宅前の緑地で毎日子供が遊んでいる&隣のおうちに赤ちゃんがいるので、とにかく毎日騒音が……その間は、正直集中しづらいです。が、静かな時は、会社にいる時よりも集中しやすくなったと思います。
    • 会社での作業は集中したいときにイアホンで音楽聞いてのですがいまは、寂しくて集中できないです。

6.ペアプロ・モブプロを行っていた方も多いと思いますが、在宅勤務になってからどのようにやっているか教えてください

※回答からいくつか抜粋

  • 全員で通話し、約1時間くらいで休憩し、そのタイミングでドライバーも変更
  • slackの画面共有またはvscodeのliveshare
  • 基本モブプロですが、個人作業の場合もチーム内で分担してやる場合が多いので、通話をつなぎながら作業しています。
  • 朝からずっとSlack通話繋ぎっぱなしです。

業務外

7.出退勤の電車に乗らなくなった分、余裕時間が増えたと思いますが、どのように活用してますか? (複数選択可)

f:id:allabout-techblog:20200422163125p:plain

8.今後、新型コロナウィルスの影響が弱くなり、出社可能となった場合、在宅勤務をどの程度の頻度でやりたいと思いますか?

f:id:allabout-techblog:20200422163136p:plain

9.在宅勤務をする上で工夫している・気を付けていることがありましたら教えてください

※記述式回答でしたが、関連性があるものが多かったので、関連性軸で出してます

  • 作業環境整備
    • ディスプレイを追加
    • 集中するための騒音対策。(音楽を聴きながら仕事をすると集中できないので)ホワイトノイズや環境音をスピーカーから流して、集中しやすいようにしています。
    • ポモドーロタイマーの導入
  • 心構え
    • 会社に行く時と同様に身だしなみを整えるとスイッチが入る
    • MTG時は事前のフォローと事後のフォローを一層手厚くする
    • 朝起きたらとりあえず着替える、コーヒーを飲むなど出勤する感を出してます
  • その他
    • 休憩時間に筋トレをする
    • 夜は散歩に出るようにしています。
    • 同居人に仕事中だとわかるようにする。
    • 定期的に換気をする

10.在宅勤務のメリットを教えてください

※記述式回答でしたが、通勤関連の方が圧倒的に多かったので、通勤関連は別軸で出してます

  • 通勤関連
    • 出勤電車に乗らなくて済むのは結構いいこと。出勤電車による疲労が減った。
    • これまで通勤時間という無駄な時間を浪費していたことを実感
    • 出勤/退勤時間がいらない
    • 無駄時間の削減(通勤)
    • 通勤ストレスが減る。割り込みが減る。お昼代も減って経済的に優しい
    • 通勤時間の有効活用
    • 通勤時間が無くなる
    • 電車通勤しなくてよくなる
    • 通話をきったら即、帰宅できること
  • その他
    • 晩ごはんを早い時間に食べられる(すごく大きいです)
    • 宅配の荷物が受け取れる
    • 部屋がどんどん綺麗になっていきます。
    • 睡眠時間が増える
    • 割り込み作業が減ったことで、まとまった時間の確保しやすくなった
    • リラックスできる
    • 自分のベストな環境で仕事ができるところ。

11.在宅勤務のデメリットを教えてください

※記述式回答でしたが、関連性があるものが多かったので、関連性軸で出してます

  • 作業環境関連
    • (全員が)自宅の仕事環境を整えるのが手間、面倒
    • 通信インフラなどが個人環境に依存すること(品質悪い人とはビデオ会議は無理)"
    • 寂しい、インターネットの速度・安定しないとつらい
  • ダイレクトコミュニケーションロス関連
    • 同僚たちを物理的に会える機会が減ること
    • 人とのコミュニケーションを撮る機会が減るので、入社してから信頼関係を構築していきたい人にとっては大変な気がします。また、一緒にコンビニに行ったりしたり、雑談ベースでの相談をする機会が減ってしまう。外に出ないのであしこしが貧弱になっていきます。
    • ほかプロジェクトの方に、気軽な相談がしにくい
    • 作業内容が見えづらい
    • 画面越しでは表情が分かりづらい
    • 表情などの視覚情報から状況を察し難い
  • 体調関連
    • 運動不足
    • 明らかに運動不足になることです。通勤の往復2時間がどれだけいい運動だったか思い知らされました。
    • 肩こりが悪化した
  • その他
    • 会社帰属意識の希薄化
    • リラックスしすぎる
    • 出勤・退勤の移動時間が無いのと、人の目が無いせいなのか、つい仕事しすぎてしまうところ
    • だらだら仕事になりがち
    • 電車に乗れない

まとめ

如何でしたか?今回はオールアバウトのエンジニアの声をそのまま伝えたくてアンケート結果には一切コメントなしでありのままをお伝えしました。
が、個人的な感想を最後に書かせていただきますと下記の3つぐらいかなーと思いました。

  • 睡眠時間が増えた、の結果が多い → みな、普段の睡眠時間が足りてなかったなー
  • 業務への集中度が変わらない、の結果が多い → 昔からリモートワークをやれるように環境を少しずつ整備してきた結果だなー
  • 今後の在宅勤務想定頻度の結果を見ると、今後在宅勤務は日常的なことになる気がするー

今の状況が一日でも早く終息して時には仲間と直接おしゃべりしながら、また時には在宅勤務で、自分たちが仕事しやすい環境で自由に仕事ができるようになってほしいですね。

閲覧ありがとうございました!今後ともよろしくお願いいたします。

Firebase における IP アドレス制限・カスタム認証について

f:id:allabout-techblog:20200324101558p:plain

先週に続き、オールアバウトの新卒1年目エンジニアが投稿する企画「テックブログ新卒週間2020」を開催します。本記事の執筆者 okutan です。よろしくお願いします。

目次

はじめに

業務でリアルタイム掲示板を Firebase で開発することになりました。
そのシステムの技術選定や試作品開発にあたり、Firebase のドキュメントや関連記事は充実していましたが、調査・検証に時間を要した点がいくつかありました。

そこで、本記事では Firebase を利用する際、時間がかかった調査結果の共有を目的として、

  1. Firebase Hosting における IP アドレス制限
  2. Firebase Authentication のカスタム認証

の使用例を示したいと思います。

本記事によって Firebase の採用を考えている方のお役に立てれば幸いです。
また、Firebase に興味がある方へ布教ができれば嬉しいです。

開発した リアルタイム掲示板のシステム構成図

今回開発したリアルタイム掲示板のシステム構成図です。
本記事では、この図における 1. IP アドレス制限2. カスタム認証 について共有します。

f:id:allabout-techblog:20200330092312p:plainリアルタイム掲示板のシステム構成図

1. Firebase Hosting における IP アドレス制限

Firebase Hosting 自体に IP アドレス制限の機能はありません。
そのため、Firebase Hosting に対する特定ファイルパスへのリクエストを Cloud Functions for Firebase に送信するよう設定し、受信した関数内でクライアント IP アドレスに基づいて静的ファイルを返すことで IP アドレス制限を実現できます。

この方法について、いくつかの注意点と共に示していきます。

Hosting へのリクエストを Functions に送る

まず、 IP アドレス制限を行える Cloud Functions for Firebase から静的ファイルを配信するために、Firebase Hosting の特定ファイルパスへのリクエストを Functions に送るようホスティング動作を設定します。
ホスティング動作は firebase.json1 で設定することが可能なので、その例を示します。

  • 管理画面の URL における //index.html へのリクエストを関数 adminIndexHtml に送信する firebase.json の設定例
{
    "hosting": {
        ...
        "rewrites": [ 
            {
                "source": "/@(|index.html)",
                "function": "adminIndexHtml"
            }
        ],
        ...
    },
... 

この設定についての説明をしていきます。

まず、 "hosting": で firebase プロジェクトにおける Firebase Hosting のホスティング動作を設定しています。
その中で "rewrites": ルール2を用いて、 "source: " に対するリクエストを "functions" に送るようにしています。
"source": は glob パターンマッチング3で指定されるため /@(|index.html) はファイルパス / または /index.html を表しており、これらに対するリクエストを、関数 adminIndexHtml に送るよう機能します。4

ここでの注意点は 2 つあります。

1つは、IP アドレス制限を行う関数 adminIndexHtml のリージョンは us-central1 にする必要があることです。これは Firebase Hosting と Cloud Functions の接続がこのリージョンしか対応していないためです。5
ただ、Cloud Functions for Firebase では関数単位でリージョンの選択ができるため、他の関数は別のリージョンにデプロイすることができます。6

もう一つは、

Firebase Hosting は、指定された source にファイルまたはディレクトリが存在しない場合にのみリライトルールを適用します。ルールがトリガーされると、ブラウザは HTTP リダイレクトではなく、指定された destination ファイルの実際のコンテンツを返します。

と記載があるように 、URL で指定されたファイルパスが実際に存在した場合、そのファイルが関数 adminIndexHtml から配信されません。
つまり、 IP アドレス制限を行いたい静的ファイル( index.html など)は Firebase Hosting から削除し、 Cloud Function for Firebase からのレスポンスでのみ配信する必要があります。
このことから、多くの静的ファイルに対して IP アドレス制限を適用する必要がある場合、 Firebase Hosting を使うことは現実的ではないと考えられます。

Functions におけるクライアント IP アドレス取得

まず、結論から示しますと、 Hosting から受信した Functions におけるクライアント IP アドレスを取得するプログラムは、私の場合、次のようになりました。

  • Firebase Hosting から受信した Cloud Functions for Firebase における IP アドレス取得 (Node.js)
const functions = require("firebase-functions");

exports.adminIndexHtml = functions.https.onRequest((req, res) => {
    const client_ip = req.headers["fastly-temp-xff"]
        .split(",")
        .pop()
        .trim();
...

このプログラムから分かるように、 HTTP ヘッダーの fastly-temp-xff 末尾から IP アドレスを取得しています。
この形式になった過程を説明していきます。

まず、IP アドレスは HTTP ヘッダーから取得できると考え、Hosting 経由の関数におけるヘッダーの中身を次に示すプログラムで確認してみました。

  • 関数 adminIndexHtml で HTTP ヘッダー内容を確認する
const functions = require("firebase-functions");

exports.adminIndexHtml = functions.https.onRequest((req, res) => {
    console.log(req.headers);
    res.send("ログ出力を Firebase コンソールで確認する");
});
  • 上記ログ出力から一部抜粋 ( IP アドレスは XXX.XXX.XXX.XXX, YYY.YYY.YYY.YYY で表現)
{
    ...
    "cdn-loop": "Fastly, Fastly",
    "fastly-client": "1",
    "fastly-client-ip": "XXX.XXX.XXX.XXX",
    "fastly-temp-xff": "XXX.XXX.XXX.XXX, XXX.XXX.XXX.XXX",
    ...
    "x-appengine-user-ip": "YYY.YYY.YYY.YYY",
    ...
    "x-forwarded-for": "XXX.XXX.XXX.XXX,YYY.YYY.YYY.YYY",
    ...
}

XXX.XXX.XXX.XXX が今回取得したいクライアント IP アドレスでした。
ログの出力結果から、 HTTP ヘッダーの fastly-client-ip, fastly-temp-xff, x-forwarded-for のいずれかから取得できることがわかります。

また、 Firebase Hosting の CDNFastly を用いているのでしょうか?
Fastly のドキュメントには、 fastly-client-ip はクライアント IP アドレスであると記載されています7

ただ、IP アドレスの偽装が可能なのか検証してみたところ、 x-forwarded-for の IP アドレスに加えて、 fastly-client-ip も偽装することができてしまいました。取得したい IP アドレスが含まれた HTTP ヘッダーの中で fastly-temp-xff のみ、(セキュリティの知識が全くない私には)偽装ができませんでした。

したがって、クライアント IP アドレスの取得は先に示したプログラムの様に fastly-temp-xff から取得する形式にしましたが、 fastly-temp-xff についての調査が不十分で安定して機能するかが私には現状分かっていないため、IP アドレス制限ができなければ致命的な問題が発生する機能・製品に対しては、認証機能を実装する必要があると考えます。

ちなみに、Node.js のモジュール request-ip を使用して IP アドレスを取得した場合、 X-Client-IP, X-Forwarded-For, fastly-client-ip から取得される可能性が高く8、 IP アドレスの偽装に対応できないため注意が必要だと考えられます。

それと、Firebase のドキュメントにある別の文脈でのやり方では req.connection.remoteAddress で IP アドレスしていましたが9、今回の様に Hosting 経由の関数である場合、取得したいクライアント IP アドレスではありませんでした。

Functions からクライアントに静的ファイルを配信する

IP アドレス制限をして配信したい静的ファイルを index.html として、 Cloud Functions for Firebase からクライアントに配信する方法を説明していきます。

まず前述の通り、Hosting からは index.html を配信しないよう削除する必要があります。削除しない場合、 Functions へリクエストが送信されないため、 IP アドレス制限が機能しません。

次に、削除した index.html を Functions のプロジェクトへ下記のように配置し、 IP アドレス制限を通過したリクエストに対してのみ配信するよう実装します。

f:id:allabout-techblog:20200330092308p:plain

  • 関数 adminIndexHtml で IP アドレス制限を行い index.html を配信する
const functions = require("firebase-functions");
const fs = require("fs");

exports.adminIndexHtml = functions.https.onRequest((req, res) => {
    const allowed_ips = ["XXX.XXX.XXX.XXX"];
    const client_ip = req.headers["fastly-temp-xff"]
        .split(",")
        .pop()
        .trim();
    const is_allowed = allowed_ips.indexOf(client_ip) !== -1;
    let html = "";
    let status = 0;
    if (is_allowed) {
        html = fs.readFileSync("./admin/index.html").toString();
        status = 200;
    } else {
        html = "";
        status = 403;
    }
    res.status(status).send(html);
});

以上の方法で index.html に対して IP アドレス制限をかけることが可能になります。
また、これらの実装を拡張することで、任意の静的ファイルに対して IP アドレス制限を行うことが可能になると考えられます。

2. Firebase Authentication と独自認証基盤の連携

なぜ Authentication に独自の認証基盤を連携したか?

理由は独自の認証基盤によるユーザーの認証状態を、 Cloud Functions for Firebase などを経由させることなく、 Firebase Realtime Database で検証するために連携しました。

今回開発したリアルタイム掲示板では、クライアントユーザーを自社がもつ認証基盤を用いて認証する必要がありました。

ただ、リアルタイム掲示板の DB は Firebase Realtime Database を使用し、クライアントから直接参照することでその恩恵10を得たかったため、セキュリティルール11で認証状態を検証する必要がありました。

そこで、Firebase Authentication と自社の認証基盤を連携させることで、セキュリティルールにおいて自社の認証基盤を用いた認証状態の検証を実現することができるため、連携を行いました。

連携方法

Firebase のドキュメント12を参考に、認証基盤において認証完了後に Firebase Authentication 用の JWT を発行するような API を実装し、 この API で返す JWT のクレーム iss に、認証基盤におけるユーザーIDを設定することで、 Firebase Authentication のユーザーIDと 認証基盤のユーザーIDを紐づけられます。

また、リアルタイム掲示板で表示するニックネームなども Firebase Authentication のカスタムクレームに保存ができ、セキュリティルールにも適用可能です。(ただし 1,000 Byte 以下)13

ただし、独自の認証基盤からクライアントに JWT を送信するには CORS を使用したレスポンスを認証基盤に実装する必要があるかと思います。

以上の方法で Firebase Authentication と独自認証基盤を連携させることが可能になります。

おわりに

本記事では、 Firebase における IP アドレス制限と独自認証基盤との連携について、調査結果・使用例を示しました。

IP アドレス制限は、 Firebase Hosting に IP アドレス制限の機能がないため、 Hosting に対する特定静的ファイルへのアクセスを Cloud Function for Firebase に送信し、受信した関数内でクライアント IP アドレスに基づいて静的ファイルを配信するよう設定することにより実現しました。
ただ、クライアントIPアドレスを取得する実装には不確定要素があるため注意が必要です。

独自の認証基盤との連携は、認証基盤に Firebase Authentication 用の JWT を返す実装をすることにより実現しました。 JWT のクレーム sub に認証基盤におけるユーザーIDを設定することで、Firebase Authentication におけるユーザーIDと認証基盤のユーザーIDを紐付けています。

新卒1年目で2通りのチーム開発を経験して学んだこと

f:id:allabout-techblog:20200325090846p:plain

先週に続き、オールアバウトの新卒1年目エンジニアが投稿する企画「テックブログ新卒週間2020」を開催します。

第3弾はオールアバウトのメディアの運営・開発を担うグループに所属する@jnamixからお送りします。

はじめに

私は入社・配属されてから4ヶ月間、広告配信に関わるWebアプリケーションの開発を行っていました。そして2019年10月にグループが異動となり、現在はメディア「All About」を中心としたWebアプリケーションの開発に携わっています。 このような新卒1年目を過ごした過程で、それぞれ異なる開発の進め方を経験しました。 今回は所属したそれぞれのグループでのチーム開発を経験して学んだことを、振り返りも兼ねてお話ししたいと思います。

広告事業時代

オールアバウトでは All About プライムアド という広告プラットフォームを提供しています。 私はこうした広告プラットフォームのシステムを構築・管理するグループに所属し、広告を運用するためのCMSの改修や機能追加などを行っていました。 グループ内で抱えているタスクは細かいものから大きいものまでありました。細かいタスクについてはそれぞれ担当者が個人タスクとして作業を行った一方で、大きいタスクについては開発リーダー(いわゆる旗振り役)を中心としたチーム開発を行っていました。 私はこのグループで、取引先向けに作られたWebアプリケーションのユーザーログイン基盤を構築するというプロジェクトに参加しました。複数のアプリをまたぐシステム構成だったため、もちろんチーム開発によるプロジェクトでした。

ここでのチーム開発の主なステップとしては、

といった具合で、ウォーターフォール開発を応用した手法をとっていました。

このチーム開発を通して学んだことは主に以下の2つです。

  • 役割分担が明確なのは、一長一短
  • スケジュールがテストの結果に依存する

これらについて掘り下げていきたいと思います。

役割分担が明確なのは、一長一短

チームでは実装作業に取り掛かる前段階で、メンバーがそれぞれ開発・改修する機能ごとに担当を割り振られました。 この役割分担で良かったことは、ガントチャートで計画した通り自分が担当する機能について集中して作業できたことです。これによって、一つのことについて深く学びを得ながら責任を持って作業を行うことができました(今回のケースでは、Cookie・セッションの仕組みなどについて学びました)。 しかし、デメリットも存在しました。それはメンバーがプロジェクト全体としての現在地の把握を疎かにしてしまいがちになっていたことです。そうすると、プロジェクトの進行具合を把握するリーダーへ負担がかかります。このような仕組みでチーム開発を進めるときは、チーム全体の進行具合を各々のメンバーがチェックしたり、フォロー体制を整える枠組みが必要だと感じました。

スケジュールがテストの結果に依存する

この開発フローの最後に立ちはだかっていたのが、テストでした。いくらスケジュール通りに作業ができても、テストの結果次第でリリースが延期になってしまう可能性があり、結果としてスケジュールがテストの結果に依存する形になっていました。 実装完了後にテストはやるにしても、その前に品質をより担保できるような開発手法(テスト駆動開発など)を取る仕組みが必要だと感じました。

メディア事業時代

2019年10月に異動となり、現在までメディア All About を代表としたメディアの運営に関わるWebアプリケーションの開発・保守を行うグループに所属しています。

このグループでも抱えているタスクは細かいものから大きいものまでありますが、基本的にほとんどのタスクがチーム開発によって行われています。

私の所属するチームでは、1週間を開発期間の1単位(スプリント)として、毎週水曜日に

  • スプリントレビュー
  • リファインメント
  • レトロスペクティブ
  • プランニング

といった形で、振り返り、要件確認、作業計画を行い、それに基づいて業務を遂行していく、という方法をとっています。いわゆるアジャイル開発の中のスクラムの枠組みです。

このチーム開発を通して学んだことは主に以下の3つです。

  • 属人化を防げる
  • アウトプットへの意識が高まる
  • 振り返りは大切、ただし改善をしなければ意味がない

これらについても掘り下げていきたいと思います。

属人化を防げる

私の所属するチームでは、大小問わず持っているタスクに全て優先順位をつけて作業計画を立て、ホワイトボードに付箋を貼る形でそれらを管理しています。(現在は、新型コロナウイルスの影響で原則在宅勤務となり、Trelloによる管理をしています) チーム内のタスクはメンバー共通のものであり、誰が担当、というものはありません。タスクを遂行するにあたり発生したドキュメントはメンバー全員に見える形で共有し、誰にバトンタッチしても良い状態を作ります。 このようにして、チーム内タスクの「属人化」を防いでいるため、チームの作業の進捗状況は常に把握できていますし、誰かに負担が集中したり、誰かがいなくなって困るという状態が発生することもありません。 個人的には途中までやっていたことを他のメンバーにバトンタッチをすることでもどかしい気持ちになることもありますが、円滑にチーム開発を進めるにはとても良い枠組みだと思いました。

アウトプットへの意識が高まる

先述の通り、チームでは1週間ごとに要件確認と作業計画を行っています。その際できるだけ多くのアウトプットを出すために、リリース納期やROIを確認しながらタスクに優先順位を付けます。そして、チームメンバーの稼働時間内に遂行できるタスクの作業計画を優先順位の高い順から立てていきます。こうして出来上がった作業計画に基づいて業務を遂行するので、自然とアウトプットへの意識が高まります。例えば、「この機能がリリースされればこれだけ収益性が高まる、コストを抑えられる」といった具合に、アウトプットをすることによって得られるメリットに注意が向きます。 このようにアウトプットへの意識が高まることは、ただの作業者ではなく、会社の収益に貢献するビジネスマンとしての視点も養われると考えられるので、とても良いことだと感じました。

振り返りは大切、ただし改善をしなければ意味がない

チームでは必ず1週間ごとに振り返りを行っています。主に使用しているのは「KPT」ですが、1週間の中で何か大きな課題があった場合はそれについて時間をとって議論することもあります。 KPTでは、Keep(続けたいこと)、Problem(課題に感じていること)をブレスト形式で洗い出し、リストアップしたものについて議論を行い、最終的にTry(次の1週間で挑戦すること)を決定します。その次の振り返りでは前週に決めたTryがきちんと達成できているか確認をします。 特に何か失敗してしまったとき、同じ失敗をしないためにも、振り返りは大切です。何ができて、何ができなかったか自分にとっての反省になりますし、他のメンバーの意見も聞ける貴重な機会になります。そして、振り返りから改善に繋げることも同様に大切です。私の所属するチームでは課題改善のためにTryを設定していますが、2週連続で同じTryを設定したことがあります。この状態を続けてしまうとチームは成長しない、ということが手にとるようにわかったので、改善することは重要だということを学びました。

終わりに

本記事では新卒1年目の私が2通りのチーム開発を経験して学んだことについてお話ししてきました。どちらが良いか、悪いかということではなく、何を学んだかということを意識して振り返ることができたのはとても良かったです。これからも一人のエンジニアとして、こうした振り返り(と、改善)を重ねながら経験を積んでいきたいと思います。

未経験でエンジニアとして新卒入社して感じたこと

f:id:allabout-techblog:20200323120018p:plain 今年も例年に引き続きオールアバウトの新卒1年目エンジニアが投稿する企画「テックブログ新卒週間2020」を開催します。

今回はオールアバウトのメディアを運営・開発するグループに所属している @rynkd08 がお送りします。

普段の業務ではLaravelを用いた開発を行っております。 同期入社の中で唯一未経験で入社した私がこの1年でどのようなことを感じ、気をつけているかについてお話しさせていただきます。

前置き

私は大学では分子生物学を専攻していました。 パソコンを使うのはレポートを書くときぐらいでほとんど触ったことがなかったです。 就職活動をし始めて3ヶ月ほど経った2018年の6月ごろにエンジニアになろうと決めてMacを購入し、とりあえずProgateを始めました。

そんなこんなで入社したのでどのくらいのレベルで入社することになったかはお分かりになると思います。

2019年5月〜6月 エンジニア研修

この期間弊社では新卒のエンジニア向けに研修が行われました。ネットワークの基礎やSQLを学んだり、最後の2週に渡ってプロダクト開発を学んだりしました。詳しい研修の内容はこちらの記事を見ていただければと思います。

新卒目線で語る!オールアバウトの新卒エンジニア研修

困っていたこと

Progateをやってただけでは実務レベルは到底追いつけない

Progateをある程度クリアしていたので大丈夫と思っていたのですがそうは行かなかったです。 基礎的なfor文の書き方や配列などはわかっていたのですがその知識を使えませんでした。

業務では配列等もかなり複雑なものになるため理解するまでにすごく時間がかかりました。

フレームワークを使うのでその知識がないと最初の段階でちんぷんかんぷんになる

フレームワーク特有の書き方(MVCモデルなど)が理解できず、 Laravelの基本的なことは教えてはもらったものの頭でわかっただけで身につくにはほど遠いという感じでした。

やって良かったこと・気をつけていたこと

ProgateをやってたおかげでHTMLとCSSはある程度わかった状態になっている

ProgateのおかげでHTMLとCSSについてはある程度できるなという印象でした。 2週に渡るプロダクト開発でも主にフロント周りを担当し、自分ができそうなデザインなどをやることでプロダクトに携わっていました。

2019年6月〜10月 配属

研修も終わりいよいよ配属となりました。未経験で入社しているのをチームのみなさんが知っていたおかげですごく細かく丁寧に説明したり話したりしてくれました。 それでもわからない単語がたくさん出てきていたのを覚えています。

困っていたこと

わからない用語だらけ

配属当初は何のことを言っているのかがわからないことが多く、大変でした。 それが会社固有の言葉なのか一般的に使われているものなのかがわからなかったです。

あとで調べても会社固有のものだともちろん調べてもわからず困り果てることも、、、

そのような時その場で質問することはもちろん、後述する夕会のおかげで解決することができました。

やって良かったこと・気をつけていたこと

わからない時は一度何がわからないかを自分の中で整理してから質問する

これはエンジニアなど職種関係なく社会人歴が浅い人が気をつけるべきことだと感じています。最初の頃はわからないことが山ほどあります。 自分が何をわかっていて何がわかっていないかわからない時も多々あります。 そのような時にただわかりませんと言っても相手を困らせてしまうだけです。

何がわからないかわからない時は理解したことを書き出してみます。 そしてここまではわかりましたと相手に伝えることで相手はじゃあこの部分をもう少し詳しく話さないといけないなと感じてくれます。 わからないことがわからない場合、わかったことを書き出してみるのも一つの手です。

とにかくメモをとる

わからないこと、わかったこと、教えてもらったこと全てを出来るだけメモするようにするといいと思います。 同じく未経験でエンジニアとして入社した先輩は1年にA4ノート2冊分ぐらいはメモをとっていました。

エンジニアとしてメモを取るべきオススメの内容はコマンドです。 何度も打つコマンドに関しては自然と覚えると思います。 しかしたまに打つコマンド(例えばKubernetesのポッドに乗り込むコマンドなど)は必要な時に覚えていないことが多いのでメモしておくと楽です。 (私はこの部分をおろそかにした結果、何度もコマンドを聞くことに....)

メモは手書きでもPCでもどちらでもいいと思いますがコマンドはPCの方がそのまま貼り付けれるので便利だとは思います。

ホワイトボードなど図に書いて説明してもらう

gitの一連の流れはホワイトボードに書いて説明してもらいました。 その図をもう一度自分でも書き起こすことで流れを理解できるようになったと思います。

f:id:allabout-techblog:20190515181844j:plain

そのほかにも設計の段階で話していることがわからなかった時はホワイトボードにまとめて書いてもらうことで視覚的に理解することができるようになりました。

夕会でその日わからなかったことを質問する時間を設ける

私が所属していたチームでは毎日30分ほど夕会を行っていました。 業務中には質問しづらかったことをこの時間に一括ですることでその日の疑問点を解消することができました。

私はたまにしかしていなかったのですが疑問点を解消した後、自分の言葉でまとめ直してアウトプットを行えばさらに知識が定着すると思います。

業務時間後にテスト駆動開発の勉強をする

テスト駆動開発を行っていたのですが配属したての時はそもそもテストとは何を書くものかということがわからなかったです。

業務の中で行うものをやっていただけではテスト駆動開発の基礎が身につかないと感じたため業務時間後に基礎的な内容で身につけようと考えました。

これを行うことでテスト駆動開発のリズムをつかめることができました。

業務終了後にも関わらず付き合ってくれたメンターの方には感謝しております。

2019年10月〜2020年3月 組織体制変更

2019年10月から組織体制が大きく変わりました。 その影響もあって開発のやり方が少し変わり最初の1ヶ月ほどは戸惑いを感じながら仕事していました。

この頃に担当していた業務としては、

  • 構造化データを導入する
  • フィード配信先を追加する
  • AIコメントシステムを導入する

などメディアのことを行っていました。

この頃になると細かい仕事が多かったこともあって業務に関して困ったことは初めの半年ほど多くはなかったです。 最初の頃には流れが全くわからなかったgitも一連の流れをスムーズに行うことができるようになりました。(2月頃になってもmasterプッシュしてしまったことがあるのでこれからも精進していきます)

困っていたこと

クラウド周りがわからない

研修の時に一通り会社で使用しているクラウド環境のことを説明してもらいましたがそれまではあまり触れる機会がなかったです。 そのため触れる機会になった時や他の業務でその知識がいるという時にわからないことが多く、研修資料をちょこっと見直すこともありました。

業務効率が悪い

タイピングスピードが遅いのはありますが、そのほかでも遅いと感じることが多々ありました。 ショートカットキーを使いこなせていなかったり、マウスを多く使うのでホームパッドとキーボードの手の行き来が頻繁だったり、、、

慣れている方はほとんどマウスを使わずキーボードを使っていました。

やって良かったこと・気をつけていたこと

わからない時はわからないと言うこと

半年過ぎたあたりから業務にも慣れてきてできることも格段と増えてきました。 しかしまだまだわからないことが多いのでその時に変なプライドを持たず素直に質問することが大事です。

タイピングの練習

タイピングの速度が基本的に遅かったのでタイピングの練習を毎日行い、スコアをスプレッドシートで管理しました。 コードと日本語では少し違うかもしれませんがタイピングスピードは早くなったと感じています。

まとめ

最初の半年ほどは本当にわからないことしかなく、毎日必死でした。 ただその時でもわからない時はわからないと素直に伝え、メモをとり、メモをとったことを自分の言葉でアウトプットする、ということを続けていると 半年ほど経った時にようやく点が繋がる感覚が出てくると思います。

何度も言っていますがわからないことだらけなのでその時にわからないと素直に言うことが大事だと感じました。 一度聞いたことを二度と聞かないようにすることも大事ですがわかるまで聞くことの方が大切だと思います。

既存batchのPHP、Laravelのアップグレードとcronjob化したお話

f:id:allabout-techblog:20200323115646p:plain オールアバウトの新卒1年目エンジニアが投稿する企画「テックブログ新卒週間2020」を開催します。

本記事はオールアバウトの運営・開発を担うグループに所属している@k_takamatsuからお送りします。 現在、オールアバウトとグループ会社のオールアバウトナビ両方で業務を行なっています。 基本的にどちらもLaravelを使ったWebアプリの開発が主です。

本日はオールアバウトナビで行なった、システムのバージョンアップとCronJob化についてお話しします。

既存batchシステムのPHP、Laravelバージョンアップ

現在オールアバウトナビ(以降ナビ社)は、「カジュアルに知性をアップデート」をコンセプトにした「SNS配信型ウェブメディア」のcitrusを運営しています。 citrus-net.jp

こちらのサービスに関するbatchシステムを改修する際に、PHPのバージョンが古くて使用できないpackageが出てきたため、バージョンアップをする必要がありました。 また、現状batchはGCPVMインスタンスで動いているのですが、この点に関して料金と何よりインスタンス自体の保守・運用などのコストが掛かる、という事からbatchをサーバレス化したいという話が上がりました。 元々ナビ社ではKubernetesでwebアプリを運用していたことから、batchシステムをCronJob化することになりました。

このような経緯から、私はこのbatchシステムのバージョンアップとCronJob化を行う事になりました。 しばらくはPHPとLaravelのバージョンアップについてお話をします、CronJobについては後ほど記載します。

既存システムの構成を把握

実際に作業を始めるにあたり、まずは現状batchシステムがどう動作しているのか知る所から始めました。

調査した結果現状の構成は以下の通りです。

古くからあるシステムという事と、社内の閉ざされた環境で動作していたため長い間アップグレードは行われていない事がわかりました。 また、LaravelもLTSの2世代前と古かった為、PHPだけでなくLaravelもアップグレードしようという事になりました。 PHPは7系に、Laravelは6系に移行する事となります。

既存システムの動きとアップデート後のシステムの動きをどう担保するか

PHPとLaravelのバージョンアップやりますって話になりましたが、実は入社するまでフレームワークを使用した開発経験がありませんでした。 またPHPに限らず、プログラム言語のアップデート対応などの知識・経験もありません。 そのため、バージョンアップ対応に自信がなく最悪バグを残したままリリースしてしまうのではないか?といった不安が当初強かったです。

また、今回はPHPのバージョンアップ、Laravelのバージョンアップ、CronJob化を一気に対応してリリースする方針となり、 それぞれ動作の担保をどのように確保するか、また問題が生じた際の切り分けをどうすればいいかという点で凄く悩みました。

色々考えた結果、動作の担保としてUnitテストを書くのが一番だと思ったのですが、既存のソースを見る限りUnitテストを入れるとなると多少リファクタする必要がありそうでした。 ですが、今回はそこまで時間を掛ける事ができないためUnitテストの導入は断念しました。 その代わり静的チェックのPHPStanを使用する事で最低限の担保を行う事にしました。 ※本当はPHPccを使用したかったのですが、サポートが終了しているためPHPStanを用いる事としました。

また、問題を切り分けるためにローカル環境に現状の動きを再現するDockerコンテナを作成し、そのコンテナ内で言語・フレームワークのアップグレードを段階的に対応する事にしました。 CronJob化については手元でPHP・Laravelの移行が完了した後に対応する事で切り分けました。

既存システムの動作をDockerコンテナで再現

まずは現状と同じ動作を行う環境を作成する必要があります。

各種設定値やcomposerで管理しているpackageなど、それぞれ調査した後にDockerfileを作成します。

ベースイメージは公式のDockerHubからphp:5.5-cliを落としてきて、それぞれ必要なモジュールをインストール、アプリイメージを作成していきます。 一から環境構築など行なった事がなかったため、この環境作りが個人的にかなり苦戦しました。

ネットの情報などを利用して記述していたのですが、次から次に襲いかかってくるエラーに心打たれ・・・それでも何度もtry & errorを繰り返してようやく動作するコンテナが立ち上がりました。 同じようにデータベース用のDockerfileも作成し、テスト用環境のデータベースからデータを取得してデータの準備も完了。 バッチとデータベースのコンテナはdocker-compose.yamlで管理する形にします。

これで無事既存システムの動きの再現を行う事ができました。 知らない事だらけだったので、この時点でかなり勉強になりました。

PHPのバージョンアップ

既存の動作をする壊していい環境を作れたので、ここからは思う存分変更を加えてPHP5.5から7系へのバージョンアップを行います。 ※壊していい環境を自分で作る事で、心理的安全を確保できたのはかなり良かったです

先ほど作成したDockerfileで使用していたベースイメージをphp:5.5-cliから7系に変更。 これでコンテナを立ち上げてPHPのバージョンを確認すると、無事php7に変更されていることが確認出来ました。 これでPHP7への切り替えは終了です。ここから実際に非推奨となった関数であったり、消されてしまった関数だったりの対応を行う必要があります。

ここからは先程述べたとおりPHPStanを用いて修正を行なっていきます。 まずは出来上がっているLaravelの環境にPHPStanを組み込んでいきます。

PHPStanの導入

導入は至って簡単で、composer.jsonに書くだけで入ってくれます。

インストールされたら、実際にチェックコマンドを実行。 /app# vendor/bin/phpstan analyse -l 0 ./app

すると沢山のエラーが出力されました。

119/119 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
------ ------------------------------------------------------------------
  Line   Console/Commands/〇〇〇〇〇〇Command.php
 ------ ------------------------------------------------------------------
  46     Call to static method info() on an unknown class Log.
  56     Call to static method info() on an unknown class Log.
  80     Call to static method beginTransaction() on an unknown class DB.
  93     Call to static method rollback() on an unknown class DB.
  94     Call to static method Error() on an unknown class Log.
  97     Call to static method commit() on an unknown class DB.
  98     Call to static method info() on an unknown class Log.
  102    Call to static method rollback() on an unknown class DB.
  103    Call to static method Error() on an unknown class Log.
  117    Call to static method info() on an unknown class Log.

こんなエラーが延々と出力されて、合計198件のエラーが検出されます。 うわー、これ手に負えないやつじゃね?って思って居たのですが、よくエラーを見てみるとunknown classがたくさんあり、かつそのclassはLaravel独自のclass名である事に気づきました。

ネットで調べてみると、Laravel 5 IDE Helper Generatorなるものがあり、LaravelのIDE補完を可能にすると上記のエラーの類は消えることがわかりました。 なのでLaravel 5 IDE Helper Generatorを導入します。

Laravel 5 IDE Helper Generatorの導入

これも同様にcomposerでインストールを行います。 composer require --dev barryvdh/laravel-ide-helper ~2.0 ※Laravel5以上からは2.0を指定するようです

インストールが完了したらProviderに登録します。

'providers' => array(
    // ↓ 下記を追記 ↓
    'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
),

php artisan ide-helper:generateを実行し、_ide_helper.phpが生成されれば終了です。

この_ide_helper.phpの中に、Laravel独自のクラスが補完されているため、PHPStan実行時に先ほどのファイルを読み込ませると良いようです。

PHPStanで補完のファイルを読み込ませる

PHPStanは実行する際に、設定ファイルを使って実行する事ができます。

そこで、以下のファイルを作成 phpstan.neon

parameters:
    level: 0
    autoload_files:
        - _ide_helper.php
    paths:
        - ../../app

実行時に設定ファイルを指定 vendor/bin/phpstan analyse -c vendor/bin/phpstan.neon これで実行結果が変化するはずです。

実際に5系から7系にあげた後、静的チェックツールで確認

Laravelのide_helperを読み込ませた結果、テストのエラー結果が変わりました。

------ -----------------------------------------------
  Line   Console/Commands/〇〇〇〇Command.php
 ------ -----------------------------------------------
  131    Undefined variable: $hoge
 ------ -----------------------------------------------
 ------ --------------------------------------------------------
  Line   Console/Commands/〇〇〇〇Command.php
 ------ --------------------------------------------------------
  154    Caught class App\Console\Commands\Exception not found.
 ------ --------------------------------------------------------
 ------ --------------------------------------------------------
  Line   Console/Commands/〇〇〇〇Command.php
 ------ --------------------------------------------------------
  160    Caught class App\Console\Commands\Exception not found.
 ------ --------------------------------------------------------
 ------ --------------------------------------------------------
  Line   Console/Commands/〇〇〇〇Command.php
 ------ --------------------------------------------------------
  166    Caught class App\Console\Commands\Exception not found.
 ------ --------------------------------------------------------
 ------ --------------------------------------------------------
  Line   Console/Commands/〇〇〇〇Command.php
 ------ --------------------------------------------------------
  168    Caught class App\Console\Commands\Exception not found.
 ------ --------------------------------------------------------

198件あったのが合計24件まで削除されました。 これでようやく対応できるエラー量となりました。

実際に検知されたエラーとしては、使用されていない変数が残っていたり、非推奨関数が使われているよってエラーだったり、こんな書き方出来ないよってエラーでした。 1つ1つエラーを解消しに行ったら対応する事ができ、最終的に静的チェックでエラーの検知を無くす事ができました。

想像していたよりも、PHP5から7への対応は比較的安易に終了する事ができたので良かったです。 ※一応不安だったので、非推奨になった関数や無くなった関数など検索しましたが、残っていませんでした。

Laravelのバージョンアップ

想定していたよりも、PHP7への移行は比較的スムーズに終わったためLaravelのアップグレードに入ります。

Laravelはアップグレードガイドを公式が出してくれています。

readouble.com

基本的にはこのアップグレードガイドに沿って行えば問題ないはずです。 今回のシステムに関してはLaravel5.1とかなり前のバージョンからのため、過去のアップグレードガイドを跨ぎながら進めました。

とは言え初めてLaravelのバージョンアップを行うため、ネットで手順などを調べてみました。 すると以下のように書いてありました。

  1. composer.jsonを修正して、ライブラリのバージョンを上げる
  2. composer updateしてみる
  3. 「composer.jsonに書いてあるライブラリはこのバージョンだと依存関係を解消できない」みたいなエラーが出る
  4. packagist.orgでバージョンと依存関係を調べつつ1.に戻る

ひとまずこのやり方に沿って進める事とします。

packageの依存関係

composer.jsonで指定しているバージョンを6.0に変更

"require": {
        "laravel/framework": "6.0.0",

実行してみると以下のようにエラーが発生

Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.
  Problem 1
    - Installation request for laravel/framework 6.0.0 -> satisfiable by laravel/framework[v6.0.0].
    - hogehoge v2.0.0 requires illuminate/support ~5.0.17|5.1.*|5.2.*|・・・v5.6.9

とあるpackageの2.0.0はLaravel5.0から5.6までしかサポートしてませんよって怒られます。 Packagistで確認しても、そのように記述されてました。

packageによっては、単純にバージョンを上げればインストールできる場合もありますが、稀にLaravel6.0に対応していない or PHP7.3に対応していないpackageがあります。 それらに関しては代用できるpackageを探してインストールする作業が必要でした。

今回は大規模にアップグレードするため、殆どのpackageを更新しなければならず・・・結構骨の折れる作業でした。 もちろんですが、使用するpackageを変更すると既存のソースコードも変更しなければならず。 早々に壁にぶつかって泣きそうでした。

artisan コマンドが使えない

無事にcomposer updateが終わっていざ、aritsanコマンドを使ってみようかと思ったのですが上手くいきませんでした。 これはLaravelアップグレードによる弊害で、アップグレードガイドを元に整えていくと無事実行されます。

その他の原因として、cacheが邪魔して動かないといった事がありました。

発生したエラーの内容を見ると以前の設定のキャッシュが残っているのが原因でした。 そのためキャッシュを削除したら上手くいくだろうと思い、実行することに。 - php artisan config:clear - php artisan cache:clear

共にエラーが発生して削除できません。

仕方ないのでbootstrap/cache配下の4つのファイルを削除して再度実行するとうまくいきました。 - packages.php - services.php - config.php - routes.php

根本の原因はこのファイルなので、このファイルを生成しないように変更します。 composer.jsonで"php artisan optimize"が走っていたのが問題でした。 以下のように削除してcomposer update -> php artisan listを実行すると正常に動作しました。

"scripts": {
        "post-install-cmd": [
            "php artisan clear-compiled",
            "php artisan optimize"
        ],
        "post-update-cmd": [
            "php artisan clear-compiled",
            "php artisan optimize"
        ],

"scripts": {
        "post-install-cmd": [
            "php artisan clear-compiled"
        ],
        "post-update-cmd": [
            "php artisan clear-compiled"
        ],

これでcomposerのupdateもできるようになったしartisanコマンドが使えるようになったので、一旦アップグレード作業を停止します。

Laravel6になったためPHPStanで動作を静的にチェック

静的チェックを行なって、今の段階でエラーがないか確認をすることにしました。

Laravel6になり内部構成も変更されているため改めてide-helperを生成します。 生成後PHPStanを実行結果、見つかったerrorは17件。 内容としては既存のpackageのエラーであったり、アップグレードによるLaravelのエラーでした。 しっかりLaravelでのエラーも捉えれているので安心ですね。

このエラーを解消していけば、Laravel6系へのアップグレード対応が完了します。

PHPStanでエラーを潰す

チェックして検知されたエラーを、Laravelのドキュメントで公開されているアップグレードガイドを元に対応していきます。

途中packageの変更に伴い、処理を書き換える必要も出てきましたが、なんとか無事にPHPStanのエラーを潰す事ができました。

PHPStanでのチェックが通ったので実際に各コマンドを実行してみる

ここまでの対応で、PHP7.3へのアップグレード・Laravel6.0へのアップグレードが一応完了しました。

実際にBatchで実行されているartisanコマンドをそれぞれ実行して動作確認をしていきます。 事前に必要なテストデータをそれぞれ作成、実行した結果と現在VMインスタンスで稼働しているbatchシステムの実行結果を比較していって動作の確認を行っていきます。

この際に現在使われていないコマンドなどが複数残っており、不要な処理とそれに関連するファイルは削除してリポジトリの掃除も行っていきます。 そうやって全部のコマンドが正常に動作するのを確認して、PHPとLaravelのアップグレード対応は無事完了しました。

VMインスタンスで動作しているbatchをGKEのpodで動かす(CronJob化)

ここまで大きな対応として3つあるうちの2つが完了しました。

残りは3つ目のCronJob化となります。 普段業務でDockerであったり、kubernetesであったり使用しているのでGKEに関してはなんとなーくわかっていたのですが。 CronJobに関しては全く知らない状態でした。

なので、まずはCronJobって何なのか調べるところからスタートです。 具体的に参考になったのは以下の記事でした、ありがとうございます。

cloud.google.com

cstoku.dev

実際にCronJobってどんな物か把握できたら、作業に取り掛かり始めます。

CronJobのyamlファイル作成

Googleが提供してくれているCronJobのドキュメントを参考にyamlファイルを作成していきます。

CronJobは基本的に1yamlファイルにつき1jobを実行します。 そのため、batchで動かしているコマンドの数ぶんCronJobのyamlファイルを書く必要がありました。

実際に作成したファイルの一部分です

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hogehoge-ranking
spec:
  schedule: "0 * * * *" # UTC時間で実行される
  successfulJobsHistoryLimit: 0
  failedJobsHistoryLimit: 0
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            name:hogehoge-ranking
        spec:
          containers:
          - name: hogehoge-ranking
            resources:
              requests:
                cpu: 〇〇
                memory: 〇〇
              limits:
                memory: 〇〇
            env:
              - name: TZ
                value: "Asia/Tokyo"
            image: GCRにあるImageのパス
            command: ["php", "artisan", "hogehogeRanking"]
          restartPolicy: Never
      backoffLimit: 1

ドキュメントなどを見て各種設定項目を追加していきました。 実行時間がUTC時間なのは気をつけなければいけません。 実行終了したpodを何世代分残すであったり、それぞれのjobを並行実行させていいのかダメなのかなど、結構考慮しなければいけない点が多かったです。 設定項目に関しては、先程紹介した記事に詳細がありますので、気になる方はそちらをご覧ください。

GKE上にデプロイしてみる

こんな感じでyamlファイルを作成したら実際にKubernetes上にデプロイして動作の確認を行います。 弊社ではCIツールとしてCircleCIを採用しています。 ※詳細は以下の記事に載っています

allabout-tech.hatenablog.com

そのためCircleCIのconfig.ymlで作成したCronJobのyamlファイルをapplyするよう修正します。 ここで問題になるのが、今までCircleCIでデプロイしていたのはVMインスタンス上でありGKE上にデプロイするような処理ではないということです。

CircleCIの設定

CronJobの確認を行う前にCircleCIのconfig.ymlを修正する必要がありました。 こちらに関しては詳細は省きますが、それぞれ必要な項目を設定して最終的に以下のようなフローにしました。

f:id:allabout-techblog:20200319110139p:plain

STGとPROに関してはそれぞれworkflowsでマージされたブランチを元に制御しています。

デプロイされた後の動作確認

まずはkubectl get cronjobでCronJobができているか確認

kubectl get cronjob

NAME                      SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hogehoge-ranking   0 * * * *             False           0              <none>                    6m59s

できている事を無事に確認できました。 jobが実行された後にjobの確認もできます。

kubectl get job

NAME                      COMPLETIONS   DURATION   AGE
hogehoge-ranking   1/1                        6m11s           6m11s

無事にjobも実行されて完了していることがわかります。 実行結果を確認しに行ったら、問題なく動作している事が確認できました。

Kubernetes Loggingでログを取得できない

CronJobの設定項目でKubernetes Loggingでログを取得できるから実行した後のpodは残さない方針に決めた、と書きました。 しかし、その大事なログが取得できない事がわかり対応する必要がありました。

そもそもDockerの標準出力としてログを確認できない

Kubernetes Loggingで確認できないという話だったので、まずは手元で動かしているDockerコンテナのログとして出力できるか確認してみました。

$ docker-compose logs batch

ログ自体は出力されるのですが、それはコンテナを立ち上げた際のログが出力されるだけで、jobで実行した際に出力しているログが表示されません。 なぜだって頭を悩ませるのですが、そもそもLaravelバージョンアップに伴ってログの設定やってないって事に気付いたのでLaravelをいじる事にしました。

Laravelでログを標準出力するまで

そもそもLaravel5.1とLaravel6.xではログの設定の仕方が異なります。 Laravel5.6からログの設定はconfig\logging.phpで設定する事になっているため、まずはファイルの作成から。

以下参考

github.com

ここでlaravelはデフォルトで標準エラーは用意されているのがわかります。

'stderr' => [
    'driver' => 'monolog',
    'handler' => StreamHandler::class,
    'formatter' => env('LOG_STDERR_FORMATTER'),
    'with' => [
        'stream' => 'php://stderr',
    ],
],

今回出力するログは開始だったり終了だったりがわかるようなログだったため、標準エラーではなく標準出力としてログを吐きたいのです。 そのためstdoutに吐くような設定を追加する必要があります。 それに関してはこちらの記事を参考にしました、ありがとうございます。

qiita.com

そんなこんなでstdoutを作成、デフォルトで標準出力を使用するように設定してLaravelの設定は終了です。

相変わらずDocker logsで表示されない

意気揚々とバッチのコマンドを実行して、Docker logsを実行! それでもログは出力されていません。

laravelの方でログが取得できているか確認しに行くと、ちゃんと今日の日付でログファイルが作成されていました。 その為Laravelでの標準出力はうまくいっている、Dockerの方で出力をキャッチできていない事がわかりました。

これに関しては、php.iniの設定が必要だったりDocker for Mac故の罠なども多々あり色々つまづくのですが今回は割愛します。

Kubernete Loggingでログが表示される!!

それぞれ必要な設定を行った後に実際にCronJobをデプロイして、podが実行されたとにログビューアを見に行くと・・・ ありました!しっかりKubernetesがログを拾ってくれていました。

I 2020-03-06T03:00:28Z [2020-03-06 12:00:28] production.INFO: ranking command start.

I 2020-03-06T03:00:28Z [2020-03-06 12:00:28] production.INFO: ranking command end.

これにて、ようやくKubernete Loggingで標準出力したいって目標は達成できました。

実際にCronJobとして処理が実行されるか確認

一通り作業が終わったら、今度はテスト環境にdeployして動作を確認します。 KubernetesUTC時間のため、CronJob.yamlファイルでの時間指定にだけ気をつけてdeploy。

それぞれ実行時間になった際に正しく動作している事を確認する事ができました。 あとは本番用のCronJob.yamlファイルとCircleCI対応を行ってdeployしたら終わりとなります。

本番のリリースはもう少し後なので、それが終わるまで気を抜かずにやりきりたいと思います。

終わりに

今回の仕事では主に以下の作業を行っています。

PHP・Laravelのバージョンアップ、Dockerアプリイメージの作成、CronJob化、CircleCIのフロー作成など。 今までの業務内容ではやる事の出来なかった内容だったので、かなり満足しています。 それも、新卒1年目で全ての作業を任せてもらえたのは大きな経験となりました。

今回の仕事を通して大事だなって思ったのは、初めての業務内容で不安がある際にその不安点をどれだけ取り除けるか工夫する事です。 私の場合手元の環境で本番と全く同じ動作を再現できた事、静的チェックツールを用いてエラーを検知する事で心理的安全を作りました。 そのおかげで、環境さえ作ってしまえばゴリゴリ変更を加えていけたので本当に大事だと感じました。

また、弊社では開発部とSREと別れていて基本的にアプリイメージの作成等はSREが行っていました。 それを、今回アプリイメージの作成、各種設定などインフラ寄りの作業も行う事ができ、自分の知見が広がった事を実感しています。 WEBアプリケーションエンジニアとしてこの辺りの知識も当然のように求められると思いますので、機会を見つけて今後も学んでいきたいです。