株式会社クイックのWebサービス開発blog

HAPPYなサービスプランナー・エンジニア・デザイナーのブログです。

傾聴力とは?「話聞いてない!」って怒られたことのある人たちへ

こんにちは。

最近、電車や駅のホームにおいて、
「おい!話聞いてんの!?」と鬼の形相になっている人を何度か見かけました。

そればかりか家に帰ってからも、
「話全然聞いてないじゃん、ふざけてんの?ねぇ!!」と鬼の形相になっている人を見かけます。

サービスプランナーのコッティです。

さて、こんなありきたりな場面、
怒られている側は話を聞いていない訳ではありません。
内容もきっと覚えてます。
でも怒っている側は話を聞いていないと感じている。
そして、気付けば相手の信頼を失っている・・

一体そのギャップは何が原因になっていて、
どうすれば信頼を失う事態を防ぐことが出来るのでしょうか。

主な原因:興味を持たれていないと感じている

原因は他にもいろいろあるかもしれませんが、
まとめると話し手が聞き手に対して、
「興味持ってない」ように捉えられる事が多いと思います。
例えば、こんな話の聞き方をした事はありませんか?

- スマホをいじりながら、適当な相槌を打つ
- 単純な相槌だけを打ってあたかも聞いている風を装う
- 気を利かせて適当に返答したけど、何かずれてる

ついついこういう行動ってしてしまいますよね。
たとえ、心では真面目に話を聞こうとしていたとしても、
上述の行動をとった瞬間、
あなたは「私の話に興味を持たない人」というレッテルを貼られることになります。
ビジネスシーンでこんな事になったら信頼失いますよね。
日常生活でも同じことです。

解決策

こんなときに役立つのが「傾聴力」というスキルです。
最近ではビジネスシーンにおいても重要性が叫ばれていますよね。
普段から意識出来る傾聴の基本を少しご紹介します。

1.態度や姿勢を相手に向ける

まずは、態度や姿勢を改めて、
「あなたの話に興味があります。」という姿勢を見せましょう。
その際に腕組みをしたり、
時計をチラチラ見る仕草は、出来るだけ止めましょう。
相手は拒絶されていると感じてしまいます。
スマホをいじりながら話聞くとか論外ですね。
相手の話を遮らない、というのも大事なポイントです。

相手の話を途中まで聞いて、何となく全容を掴んだ気になってしまい、
話を遮って自分の考えや理解を喋り出す。こんなことやってませんか?

2.相手を真似する

相手の仕草や姿勢、声のトーン等を相手に合わせてみましょう。
ミラーリング」と呼ばれる手法です。
食事や会食の際に、食べ物を口に入れるペースや、
飲み物を飲むペースを合わせると親近感が増すと言われますよね。
あれと同じことです。

真似する関係で言えばもう一つ、
相手が話した事を繰り返す事も傾聴の手法です。
しっかり理解してくれる、共感してくれるという印象を与える効果があり、
「バックトラッキング」と呼ばれています。
例えば、こんな流れでしょうか。
ほぼ相手の会話を繰り返しているだけなのにポジティブなやり取りが飛び交ってます。

A:「今日ね、恵比寿に飲みに行くの~」

B:「え、今日恵比寿に飲みに行くの~?」

A:「そう、美味しい餃子のお店に行くんだ~」

B:「美味しい餃子のお店に行くんだ~良いね!」

ここで最初の返しで、こんな返答をしてしまうとどうでしょうか?

B:「恵比寿か、オシャレなイタリアン行くの?良いね!」

本当は美味しい餃子のお店に行くことを嬉しく伝えたいのに、
何だか興ざめして、会話終わらせてしまうこともありますよね。


3.相手の言葉を言い換えてみる

少し難易度が上がってしまいますが、
相手の話したことを自分の言葉に変換して返す手法です。

これが出来ると、相手はきちんと話を聞いてくれて、
しかも理解してくれたと信頼がグッと増します。

相手の話を要約して返せると理想的かもしれませんが、
相手の話の逆説を言うだけでも立派な言い換えになります。
上手だなーと思う人は「逆に〇〇ならこうなりますか?」、
といった言い方をしている場合もあります。

最後に

話を聴くというのはコミュニケーションの基本です。
コミュニケーション能力の高い人というのは、
聴く能力の高い人だと個人的には思っております。

今回記載した内容はビジネスでも日常でも、
少し意識すれば出来ることかと思います。
私はプランナーという職種柄、いろんな人にヒアリングや提案する事も多いです。
聴く姿勢をとることで、相手から信頼を得るというのは非常に大切な事だと思っています。

ちなみに私の場合は、業務でのヒアリングを実施する際に、
「バックトラッキング」と「相手の言葉を言い換えてみる(要約)」を抱き合わせて、
使っていることが多いです。

反復、反復、反復(反復の間に情報整理して)、要約で確認するという流れです。
こちらから確認を挟むことで、相手も改めて内容を整理して、
誤りや追加情報があると色々と話しをしてくれるという副次効果もあったりします。

「話聞いてないじゃん!」と怒られる人が少しでも減りますように。



\\最高のサービスを一緒に作る仲間を超募集!! // 919.jp

業務をヒアリングする際に心に留めているちょっとしたこと

こんにちは、サービスプランナーのいのっちです。

弊社ではプランナーの仕事の1つとして、業務をシステムを用いてどのように改善するか企画したり、その推進をしたりします。

今回は、現状の業務をヒアリングする際、私が気にかけていることをいくつかご紹介したいと思います。

1.業務がかたいのか、やわらかいのかを考えながら聞く

業務のシステム化による効率化はとても効果があり、今まで何時間もかけていた仕事が一瞬で終わることも少なくありません。

しかし、一方でシステム化するということは、手作業に備わっている柔軟性を失うことになります。

立ち上げたばかりの事業など、事業や仕事自体が手探りであるときは、日々、仕事のやり方を変更していくことも珍しくありません。これは業務がやわらかい状態です。

この状態の時は、「今、システム化すべきなのか?まだ、しない方が良いかも知れない」という事を心に留めつつ聞いていきます。

2.ミスへの対応をどうしているか引き出す

担当者から業務の流れや詳細を伺うときは、多くの場合、定められた業務手順を説明されると思います。

それをそのままシステムにしてしまうと大きな落とし穴にハマってしまいますので注意が必要です。

なぜならば、人が作業をすると、どうしても何かしらミスをしてしまうからです。
選択ミスをして送ってはいけない宛先にデータを送ろうとしていたり、誤ったデータを登録していたり様々な箇所でミスをしてしまいます。

ミスに対応できるシステムでなければ業務に耐えることはできません。
ミスを対応できるようにするには、ミスに気がつくことができることと、ミスを修正しリカバリーすることができる必要があります。

手作業で業務を行っていると、意識をしていなくても、データや操作の間違いを発見することが多々あります。これは業務手順として定義されないですが、データをチェックするポイントとなっています。

これらは当事者が意識していないため説明されないことが多いですが、うまく捉える必要があります。

そのような時は次のように、過去のトラブル事例を伺うような形で聞くと、うまく引き出せます。

「過去、間違いが入ったまま(プロセスを)進めてしまい、後で気がついて何とかしたことってありますか?

そうすると、何かしら過去のことを語ってもらえると思います。
さらに下記のことも深堀りして聞いていきましょう。

  • 誰が気がついたか
  • 何を見て気がついたか
  • どうやってリカバリーしたのか

3.改善ポイントが無いか探すためアレコレ聞く

業務の手順は増やす事は多いですが、業務をやりながら自ら減らすことはなかなかできないものです。

折角の機会ですから、今の業務を無くして省力化できないか考えていきたいですね。
是非、業務担当者とディスカッションして、改善のヒントを引き出しましょう。

私の場合、下記のような問いかけをキッカケにしたりします。
今やっている○○のチェック。もしやらなかったらどうなると思います?

そうすると、例えば、「入力時の担当者がコピペミスを意外としていて、そのまま出すとクレームとなるので怖い」というような意見をもらえたりします。

この場合、前プロセスの入力画面のUIが問題かも知れませんし、同じ概念のデータ項目があり混乱を招いているのかも知れません。

さらに深堀りをする価値がありそうですね。

最後に

業務の流れを担当から教えてもらう機会は改善の大チャンスです!
積極的に質問して過去の経緯・背景や将来の希望を引き出しましょう。
きっと良くするポイントが見つかるはずです。



\\最高のサービスを一緒に作る仲間を超募集!! // 919.jp

記事がバズってサイトがダウン!?の舞台裏

サービスプランナーのyumeです。
私は、数千件の記事を扱うメディアサイトを担当しているのですが、嬉しいことにたまに突然アクセスが集中することがあります。いわゆる「バズ」ですね。

このとき、開発運用チームでは「編集チームが丹精込めて作った記事が日の目を見るチャンス!1人も逃したくない!」と思い対応するわけですが、具体的には何をしているのか?という流れを紹介したいと思います!

状況)サイトが重くなる

自分たちの施策やイベント等、予め対策ができる場合もあるのですが、だいたいは準備していないときに限ってサイトダウンは訪れるんですよね。 事象としては、ページの表示が異常に遅い、ステータス500を連発する、という感じです。

1)アラートが飛ぶ

私たちが運用しているサイトはすべて監視ツールを入れて負荷状況を見ています。
負荷が高まるとチャットにアラートが飛ぶ仕組みです。

業務時間外でチャットを見ていない場合もあるので、気づいた人が担当者に連絡したり、可能な範囲で状況をチェックしたりします。

2)駆けつける

ミーティングから抜け出したり、飲み会から抜け出したり、帰りの電車に乗っていたところを折り返したり。
このときはめんどくさいというより使命感で燃えまくっています。

今回は、インフラエンジニア(弊社ではSREチーム)×サービスプランナーで対応した場合の、プランナー目線の流れを記載します!

3)アクセス集中ページの特定

まず、GoogleAnalyticsのリアルタイムを確認

f:id:aimstogeek:20200312142514p:plain
GAリアルタイム

サーバが完全に死んでいない場合は、このページを見ると、見慣れないページにアクセスが集まっていることに気づけます。
※サーバが完全にお亡くなりになった場合は確認ができなくなるのでそのときはアクセスログを見るしかないです。

各ページの仕様をだいたい把握しているので、ページが分かると、Web/DB/その他のどこに負荷がありそうか推測が可能。

私がこのように対応しているとき、SRE側でもサーバ負荷チェックやアクセス集中パスの調査を行ってくれます。
結果、「とりあえずDBを増やそう」などの一旦の対応方針を決めることができます。

4)原因の特定

流入経路の確認

引き続きGoogleAnalyticsのリアルタイムで、該当ページへのアクセス元をざっくり割り出し

f:id:aimstogeek:20200312152614p:plain
GAリアルタイム_トラフィック
担当サイトのケースでは、

  • ソーシャル
  • オーガニック

のどちらかで、だいたいオーガニックです。
要は、「話題になったキーワードでSEO上位を獲得していた」ケースですね。

今回も、ここで「オーガニックの流入が多いぞ!」となったパターンで紹介します。

検索キーワードの確認

ページと流入経路が特定できたので、どのキーワードでアクセスが集まっているのかを確認します。
これには、SearchConsoleを使用。リアルタイムの情報は見れませんが、流入キーワードの傾向はわかるので結構役に立ちます。

こんな感じで、ページのURLを指定してフィルタをかけて…

f:id:aimstogeek:20200312152621p:plain
SearchConsoleでページにフィルタ設定
キーワード傾向を抽出。
f:id:aimstogeek:20200312152632p:plain
通常時のこのページの流入キーワードが分かる
こうすることで、検索数が急激に増えたキーワードのあたりをつけることができます。

SNSで話題調査

キーワードのあたりがついたので、今度はそのキーワードがSNSでどのように言及されているかを調査。
これには、「Yahoo!リアルタイム」が便利です。
https://search.yahoo.co.jp/realtime

塗りが多くてもはやなにを表しているか分からないと思いますが、SNSの検索結果に加えて、右側に言及数の推移も表示されて便利です。

f:id:aimstogeek:20200312152636p:plain
わかりにくいけど非常に便利だぞ!

これらをかけ合わせて、以下を推測・予想!

  • どうしてそのキーワードが話題になったのか
  • いつから話題になったのか
  • それがどのくらい継続しそうか

担当サイトのケースでは、テレビで特集が組まれたキーワードだったとか、医療ドラマで意味深につぶやかれたキーワードだったとかが多いです。
ちなみに、医療ドラマの場合は編集チームの誰かが絶対見ているので、「〇〇ってワードでバズったんだけどなんかあった?」と聞けば一発で答えてくれます。笑

原因が分かったので、今後の予測をたて、対応を検討!

例:テレビ関係

  • 放送された瞬間とその後のCMがタイミングとしてはピーク
  • そこからは割と急速にアクセスが落ちるので、その時点の負荷に耐えられる構成にして一旦の対応を完了

例:Yahoo!ニュースに載ったケース

  • 数時間単位でコンスタントにアクセスが来る
  • どこでピークがくるか不明。余裕を持ったインフラ構成にしておく

この辺の予測はもう経験と勘です。他社の事例とか超知りたい。

5)対応

実際の対応はインフラがメインなので、SREチームがやってくれます。臨機応変に対応してくれるのでとても心強い。

実際の指示も、「Webサーバ3台増やして」なんて具体的なことではなく、「医療ドラマでこのページがバズってるみたいなんだけどどうしよう!」というような情報を共有して、一緒に対応を考えることが多いです。

まとめ

バズは嬉しいものですが、対応できなければせっかく来てくれたユーザーとの縁が持てなくなってしまいます。
私たちも多くの失敗を経て、現時点ではこのような形で対応していますが、まだまだ対応しきれないことも多く。。
オートスケールもしたいし、そもそももっとキャッシュとか活用して負荷も減らせるのでは?とか、考えることはいっぱいあると思います。日々精進ですね!



\\世の中とユーザーの動きを見ながらアレコレ動きたい仲間を募集!! // 919.jp

Laravelの.envについて調べてみた

どうもソフトウェアエンジニアのぱふゅーむです。
最近Laravelの.env周りで少しトラブったので復習の意味も込めて、
Laravelの.envの仕様について調べてみました。

今回は下記2点を中心に.envについて説明していきます!

  • .envファイルがどのように環境変数として設定されるか
  • .envファイルの値を取得する際に気をつけなければならないこと

.envファイルがロードされるタイミング

.envはIlluminate\Foundation\Bootstrap\LoadEnvironmentVariablesのbootstrapメソッドでロードされています。
.envをロードする前に$app->configurationIsCached()でキャッシュの存在をチェックしているようです 。

<?php

/**
 * Bootstrap the given application.
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @return void
 */
public function bootstrap(Application $app)
{
    if ($app->configurationIsCached()) {
        return;
    }

    $this->checkForSpecificEnvironmentFile($app);

    try {
        (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
    } catch (InvalidPathException $e) {
        //
    }
}

ちなみにbootstrapメソッドが呼ばれるタイミングはpublic/index.phpの以下の部分です。
メッセージがイカしてます笑

<?php

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

キャッシュの存在をチェックするconfigurationIsCachedメソッドが定義されているのはIlluminate\Foundation\Applicationです。

<?php

/**
 * Determine if the application configuration is cached.
 *
 * @return bool
 */
public function configurationIsCached()
{
    return file_exists($this->getCachedConfigPath());
}

/**
 * Get the path to the configuration cache file.
 *
 * @return string
 */
public function getCachedConfigPath()
{
    return $this->bootstrapPath().'/cache/config.php';
}

bootstrap/cache/config.phpが存在する場合は、.envをロードせずbootstrap/cache/config.phpを返しているようです。
bootstrap/cache/config.phpphp artisan config:cacheで作成されるファイルです。
コマンドを叩いて作成されたファイルを確認してみました。

bootstrap/cache/config.php

<?php

return array (
  'app' => 
  array (
    'name' => 'hoge',
    'env' => 'local',
    'debug' => true,
    'url' => 'https://hoge/fuga',
    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',
    'fallback_locale' => 'ja',
    'key' => '',
    'cipher' => 'AES-256-CBC',
    'log' => 'single',
    'log_level' => 'debug',
    'providers' => 
    array (
      0 => 'Illuminate\\Auth\\AuthServiceProvider',
      1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      2 => 'Illuminate\\Bus\\BusServiceProvider',
      3 => 'Illuminate\\Cache\\CacheServiceProvider',
      ...
    'aliases' => 
    array (
      ...
  'auth' => array(
    ...

config/配下のファイルが全てキャッシュされているのが分かります。 php artisan config:cacheを叩くと.envを読み込まず、こちらのキャッシュファイルを読み込むようになります。

cacheされたファイルが存在しない場合、.envファイルの値はどのように環境変数に設定されるのでしょうか?
コードを追ってみると以下のように定義されていました。

Dotenv/Loader

<?php

/**
 * Set an environment variable.
 *
 * This is done using:
 * - putenv,
 * - $_ENV,
 * - $_SERVER.
 *
 * The environment variable value is stripped of single and double quotes.
 *
 * @param string      $name
 * @param string|null $value
 *
 * @return void
 */
public function setEnvironmentVariable($name, $value = null)
{
    list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);

    // Don't overwrite existing environment variables if we're immutable
    // Ruby's dotenv does this with `ENV[key] ||= value`.
    if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
        return;
    }

    // If PHP is running as an Apache module and an existing
    // Apache environment variable exists, overwrite it
    if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
        apache_setenv($name, $value);
    }

    if (function_exists('putenv')) {
        putenv("$name=$value");
    }

    $_ENV[$name] = $value;
    $_SERVER[$name] = $value;
}

最終的にはputenv環境変数に定義していました。
つまり.envに設定された値は、カレントのリクエストを実行している間のみしか環境変数として存在しません。

cacheした場合に気をつけなければならないこと

Laravelには環境変数を取得するヘルパ、env()が用意されています。
Illuminate\Support\helpers/phpに定義されているので中身を見てみます。

<?php

/**
 * Gets the value of an environment variable.
 *
 * @param  string  $key
 * @param  mixed   $default
 * @return mixed
 */
function env($key, $default = null)
{
    $value = getenv($key);

    if ($value === false) {
        return value($default);
    }

    switch (strtolower($value)) {
        case 'true':
        case '(true)':
            return true;
        case 'false':
        case '(false)':
            return false;
        case 'empty':
        case '(empty)':
            return '';
        case 'null':
        case '(null)':
            return;
    }

    if (strlen($value) > 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) {
        return substr($value, 1, -1);
    }

    return $value;
}

基本的にはgetenv()で取得した環境変数を返しているだけです。
つまり、php artisan config:cache時にControllerや、Model等config/配下以外で.envファイルの値をenv()ヘルパを使用して取得しようとすると .envファイルをロードしないため、nullが返ってきてしまいます。
その回避策として.envの値を参照する時はヘルパ、config()を使用します。

Illuminate\Foundation\helpers.phpをみてみます。

<?php

/**
 * Get / set the specified configuration value.
 *
 * If an array is passed as the key, we will assume you want to set an array of values.
 *
 * @param  array|string  $key
 * @param  mixed  $default
 * @return mixed
 */
function config($key = null, $default = null)
{
    if (is_null($key)) {
        return app('config');
    }

    if (is_array($key)) {
        return app('config')->set($key);
    }

    return app('config')->get($key, $default);
}

configの値を取得してくれていますね。
env()はconfig内のみで使用するよう注意が必要です。

.envの運用について

今回の調査で.envファイルの値がどのように環境変数として設定されるか分かり、勉強になりました。
.envは便利なファイルですが、一歩使い方を誤るとクリティカルなバグの温床になってしまいます。
ちなみに私が参画しているプロジェクトでは.envを運用していません。
正しく言うと.envは設定していますが、.envに設定した値を直接apache環境変数に設定しています。

この運用は下記のようなメリットがあげられます。

  • configをキャッシュした時に、上記に記述したようなバグを防げる
  • アプリケーションより下のレイヤーで設定することによって、若干だがパフォーマンスの向上が期待できる

しかし反対に、devやstgなど各環境毎に.envの値をapacheに設定してあげる必要があるため、 煩わしさもあります。

今回の調査を参考に、.envのベストな運用を考えていきたいと思います。

Enjoy developing!



\\『真のユーザーファーストでマーケットを創造する』「ありがとう」で溢れる仲間を募集中です!! // 919.jp

実践!GitLab CI/CDでのジョブ実行基盤改善

こんにちは。2019/8月入社SREチームのくぼっきーです。
今回は弊社のCI/CD環境周りをGitLab CI/CDでスマートに!という内容でお送りします。

前提

  • AWS × Jenkins × GitLabCE on EC2 な環境

課題

  1. 業務バッチとCI/CDジョブが混在していて止まってはいけないジョブがどれなのか担当者じゃないとわからない。(仮に把握してもその後ジョブはどんどん生まれていく
  2. ジョブ間の依存関係がよくわからない。(Jenkinsのpipelineは敷居高い、、、
  3. Jenkinsのジョブ定義をバージョン管理できていない。いつの間にか動かなくなったなんてこともざらにある。
  4. 影響範囲を限定するためプロジェクト毎に専用のJenkinsスレーブを立てているが、スレーブノード自体の管理&認知負荷が辛い。(ノード毎にタグ管理とかしたくない、そして分けても過負荷で止まる
  5. Gitリポジトリとの連携はServer hooks使い始めたけど、ここの作りこみをやっていくと辛い未来が待っている。

GitLab CI/CDの良いとこ

  • ☞ 1.の課題解消 止まるとサービス影響のある業務バッチ(Jenkins)と、止まっても直接的にサービス影響が生じないビルド/デプロイジョブ(GitLab CI/CD)とで棲み分けがされていくことが期待できる。
  • ☞ 2.の課題解消 ジョブとリポジトリが紐づく & 強制的にパイプライン表現になるので依存関係がわかりやすい。 f:id:aimstogeek:20200227172829p:plain
  • ☞ 3&5.の課題解消 Gitリポジトリとの連携が非常にスマートになる。特につくりこまなくても様々な開発フロー要件にフィットさせられる。
  • ☞ 4.の課題解消 ジョブ実行ノードをオートスケールアウト・インさせれるので一度作ってしまえば基本的に放置でおk。ジョブ実行基盤をプロジェクト毎に管理することから解放される。

  • Jenkinsリモートビルド機能を活用すれば既存Jenkinsジョブを流用しつつ柔軟に開発フローを組むのも簡単。
    参考:JenkinsでJobを起動して、終了まで待つシェルスクリプト
  • スポットインスタンス運用&営業時間内外でインスタンスのアイドリング数を変更出来たりとコスパ最強!!
  • その他まだまだ魅力満載\(^_^)/(機能多過ぎで触りきれない
    参考:GitLab機能一覧

構成概要

f:id:aimstogeek:20200228104843p:plain

  • GitLab:gitリポジトリサーバ。
  • GitLab Runnerベースマシン: ジョブ実行制御用ノード。リクエスト数に応じてスポットインスタンスでジョブ実行ノードをスケーリング。
  • GitLab Runnerジョブ実行ノード: 実際に処理が実行されるインスタンス

全体の流れ

※既にGitLabサーバが立っていることを前提としてます

  1. GitLab Runnerジョブ実行ノード用AMIを用意
  2. GitLab Runnerベースマシンセットアップ
  3. CI/CDジョブを実行してみる
    ※とりあえずCI/CD機能だけ試してみたい!って人はgitlab.comを利用すれば3のステップだけでお試しできます

Let's try

1. GitLab Runnerジョブ実行ノード用のAMIを用意

概要

ベースマシンがジョブ実行ノード起動時に使用するAMIを用意します。起動処理はdocker-machineで行われ、ジョブ実行ノードで自動的にdockerがセットアップされます。

動作要件

  • GitLab Runnerベースマシンから、22番ポートを使用してパスフレーズ無し公開鍵認証でsshログイン出来ること
  • GitLab Runnerベースマシンから2376番ポートで通信出来ること(docker socket通信で使用)

docker-machineがデフォルトだとubuntuユーザでsshログインしにいくため、今回の検証ではAMIストアパブリックイメージのUbuntu 19.04(ami-0b50bf6a426e13bf9)を利用しました。デフォルトのままであればSSH鍵も自動生成してくれるのでお手軽です。


2. GitLab Runnerベースマシンセットアップ

概要

下記動作要件を満たすコンテナを立ち上げ、GitLab Runnerの設定をします。

動作要件

  • GitLabサーバとSSH、HTTP or HTTPS通信が出来ること
  • dockerコマンドが動作すること
  • docker-machineコマンドが動作すること
  • gitlab-runnerコマンドが動作すること

セットアップ

  • 下記のdockerfileを作成し、コンテナを起動します。

Dockerfile

FROM gitlab/gitlab-runner:latest
RUN apt update -y && apt install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \
    add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
RUN apt update -y && apt install docker-ce -y && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

docker build -t gitlab-runner-base "Dockerfileを配置したディレクトリ"
docker run -d -t --name gitlab-runner-base -i gitlab-runner-base
  • 下記コマンドを環境に合わせパラメーター設定し、実行

各パラメーターはこちらを参考に設定してください https://docs.gitlab.com/ee/ci/runners/README.html
https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/
https://docs.gitlab.com/runner/executors/docker_machine.html
http://docs.docker.jp/machine/drivers/aws.html
※docs.docker.jpのページは記載されてるオプションの一部でスペル間違いがありハマったので、正確なスペルはdocker-machineコマンドのhelpを確認することお勧めします

Command

docker exec -it gitlab-runner-base gitlab-runner register \
 --non-interactive \
 --name autoscaler \
 --limit 10 \
 --url https://gitlab.com/ \
 --registration-token XXXX \
 --executor docker+machine \
 --tag-list sample_runner \
 --custom_build_dir-enabled \
 --docker-image docker:stable \
 --docker-privileged \
 --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" \
 --docker-volumes "/builds:/builds" \
 --docker-pull-policy "if-not-present" \
 --machine-max-builds 0 \
 --machine-idle-nodes 1 \
 --machine-idle-time 1800 \
 --machine-machine-driver amazonec2 \
 --machine-machine-name=runner-default-%s \
 --machine-machine-options "amazonec2-ami=ami-0b50bf6a426e13bf9" \
 --machine-machine-options "amazonec2-access-key=XXXX" \
 --machine-machine-options "amazonec2-secret-key=XXXX" \
 --machine-machine-options "amazonec2-instance-type=t2.micro" \
 --machine-machine-options "amazonec2-region=ap-northeast-1" \
 --machine-machine-options "amazonec2-root-size=16" \
 --machine-machine-options "amazonec2-security-group=gitlab-runner" \
 --machine-machine-options "amazonec2-ssh-user=ubuntu" \
 --machine-machine-options "amazonec2-tags=Name,gitlab-runner" \
 --machine-machine-options "amazonec2-vpc-id=vpc-XXXX" \
 --machine-machine-options "amazonec2-subnet-id=subnet-XXXX" \
 --machine-machine-options "amazonec2-zone=a" \
 --machine-machine-options "amazonec2-private-address-only" \
 --machine-machine-options "amazonec2-request-spot-instance" \
 --machine-machine-options "amazonec2-spot-price=" \
 --cache-type s3 \
 --cache-path gitlab-runner/cache \
 --cache-shared=true \
 --cache-s3-server-address s3.amazonaws.com \
 --cache-s3-access-key XXXX \
 --cache-s3-secret-key XXXX \
 --cache-s3-bucket-name "s3bucket_name" \
 --cache-s3-bucket-location ap-northeast-1 \
 --machine-off-peak-periods "* * * * * sat,sun *" \
 --machine-off-peak-periods "* * 0-9,18-23 * * mon-fri *" \
 --machine-off-peak-timezone Asia/Tokyo \
 --machine-off-peak-idle-count 0 \
 --machine-off-peak-idle-time 60

ログを確認し下記のように最終的に "Machine created" と出力されていればOK

Log

# docker logs -f gitlab-runner-base

~~~
~~~
Configuration loaded                                builds=0
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Running pre-create checks...                        driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
Creating machine...                                 driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
(runner-39148577-runner-default-1582858635-dce88b79) Launching instance...  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
(runner-39148577-runner-default-1582858635-dce88b79) Waiting for spot instance...  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
(runner-39148577-runner-default-1582858635-dce88b79) Created spot instance request sir-3mdrb6sg  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Waiting for machine to be running, this may take a few minutes...  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
Detecting operating system of created instance...   driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
Waiting for SSH to be available...                  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Detecting the provisioner...                        driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Provisioning with ubuntu(systemd)...                driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Installing Docker...                                driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
Copying certs to the local machine directory...     driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
Copying certs to the remote machine...              driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Setting Docker configuration on the remote daemon...  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Checking connection to Docker...                    driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
WARNING: Failed to process runner                   builds=0 error=failed to update executor: no free machines that can process builds executor=docker+machine runner=39148577
Docker is up and running!                           driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env runner-39148577-runner-default-1582858635-dce88b79  driver=amazonec2 name=runner-39148577-runner-default-1582858635-dce88b79 operation=create
Machine created                                     duration=1m0.152015073s name=runner-39148577-runner-default-1582858635-dce88b79 now=2020-02-28 02:58:15.978711696 +0000 UTC m=+86584.947166950 retries=0

  • ジョブ同時実行数を設定
    デフォルトではジョブ同時実行数が1となっているため、必要に応じて同時実行数を増やす
docker exec -it gitlab-runner-base bash
vi /etc/gitlab-runner/config.toml

先頭行にあるconcurrentの値を希望の数に設定

concurrent = xx
~~~

3. CI/CDジョブを実行してみる

参考:GitLab CI/CD Pipeline Configuration Reference

.gitlab-ci.ymlサンプル

stages:
  - codecheck
  - build_container
  - deploy_feature
  - e2etest
  - deploy_develop
  - deploy_production

variables:
    GIT_CLONE_PATH: ${CI_BUILDS_DIR}/${CI_PROJECT_NAME}/${CI_COMMIT_REF_SLUG}

static_code_analysis:
  stage: codecheck
  image: docker/compose:latest
  script:
    - echo static_code_analysis

code_test:
  stage: codecheck
  image: docker/compose:latest
  script:
    - echo code_test

build_container:
    stage: build_container
    image: docker:stable
    script:
      - echo build_container

deploy_feature:
    stage: deploy_feature
    image: docker:stable
    script:
      - echo deploy_feature
    environment:
      name: feature/${CI_COMMIT_REF_SLUG}
      url: https://${CI_COMMIT_REF_SLUG}.example.com

e2etest:
    stage: e2etest
    image: docker:stable
    script:
      - echo e2etest

deploy_develop:
    stage: deploy_develop
    image: docker:stable
    script:
      - echo deploy_develop
    only:
      - develop
    when: manual
    environment:
      name: develop
      url: https://dev-example.com

deploy_production:
    stage: deploy_production
    image: docker:stable
    script:
      - echo "deploy_production"
    only:
      - master
    when: manual
    environment:
      name: production
      url: https://prd-example.com

  • GitLabのPipelinesページでパイプラインが実行されてることを確認 f:id:aimstogeek:20200228123312p:plain

    f:id:aimstogeek:20200228132753p:plain

  • MR画面にパイプラインの実行結果が表示&レビュー用環境へのリンクが自動付与(Review Apps機能)されレビュー効率が大幅アップ\(^_^)/ f:id:aimstogeek:20200228174802p:plain

以上!

コメント

本記事上では一部機能にしか触れてませんが、その他機能含め感触としてGitLabとてもおすすめです!
やはりGitLabはリポジトリとCI/CDが同じサービス内で統合されているという点で利点が大きく、CI/CDでやりたいおおよそのことがスマートに実装出来る印象を持ちました。
またプロジェクト管理機能が充実しているので、この点も上手く使いこなせればよりスマートなDevOpsに近づくのではないかと思います。

世の中ではGitHubのほうが人気ですが、今回色々と触ってみたことで個人的にはGitLab推しになりました!ぜひ皆さんもGitLab使ってみてください。

Enjoy develop!



\\『真のユーザーファーストでマーケットを創造する』「ありがとう」で溢れる仲間を募集中です!! // 919.jp

チーム形成期に輪読会をやってみた!

Web室LT大会実行委員長兼ブログ雑用担当のhamanokamiです!

現在ありがたいことに続々と新規メンバーが増えまして(うれし~)、 私がメインで関わっているプロダクトチームのメンバーのうち、3分の2以上が入社1年未満なんです!!!

チームビルディング理論で有名な「タックマンモデル」に当てはめると、バッチリ「形成期」ですね!

mitsucari.com

「形成期」は、メンバーのほとんどがお互いを知らない、 かつ新規参画メンバーは事業や業務のことを知らない状態のため、 『コミュニケーションと情報の「量」が重要』とのこと!!!

ということで、事業・業務・メンバーを知る機会を定期的につくってまして、 その一環でうちのチームでは輪読会を隔週で開催しています!

輪読会とは

人々が集まって、同じ教科書などの本を読み、その内容について意見を交わすことを意味する語。事前に決められた担当者が、本の内容を訳したりまとめたりしてから、他の参加者が理解できるように発表する形式がとられることも多い。

引用元:https://www.weblio.jp/content/%E8%BC%AA%E8%AA%AD%E4%BC%9A

輪読会の目的

輪読会の目的は2点です!

  • チームで成果を出すために大事な考え方をすり合わせたい
  • 個人の当たり前(考えていること)を出してもらうきっかけをつくりたい


特に後者のほうが大事と思っていまして、普段業務では明文化されていないところをあぶり出すことで、 「そういう考えで仕事をしていたんだ!」等の発見があり、 それが学びや議論につながって、前者のすり合わせをより良いものにしてくれます!

選んだ書籍と理由

で、今輪読会で読んでいる本は、 「アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~」 です。

book.mynavi.jp

名著ですよね!!!

この本にした理由は下記のとおりです。

  • そもそも見積りと計画づくりを何のためにするのかを全員が正しく理解している状態にしたい
  • 見積りや計画の精度を上げるための知識、手法の習得


この本は、 「ユーザに価値を迅速かつ正確に届ける続けるために、 ステークホルダーが正しい意思決定ができる状態を保ち続けるには、 正しい見積りと計画づくりができていないといけない」という考えの元、 どうすればそれが実現できるかの手法を教えてくれます。

決してこの世に銀の弾丸はないので、その手法をそのまま自分たちの開発フローに取り入れようとは思っていないのですが、 この本の根底にある考えをメンバー全員が正しく理解して仕事をすることで、 チームの成果が格段に良くなると私は信じています!!!

そのため今回は「アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~」を選びました。

輪読会の進め方

輪読会は下記のように進めています。

事前準備

  • 要約担当が輪読会までに対象の範囲を要約
  • それ以外のメンバーは事前に対象の範囲を一読

輪読会(60min)

  1. 要約担当が要約を発表
  2. 要約の内容も含めて議論や感想を言い合う
  3. 次回の要約担当を決定

輪読会をやってみてよかったこと

現在進行形で輪読会を続けていて、やってみてよかったと思うことはいろいろとあるのですが、 その中でも輪読会からまた新たなすり合わせ機会が生まれたことが特に良かったと思っています。

ある輪読会のときに、入社したばかりの敏腕エンジニアの方から、 「実際に全員でプランニングポーカーを試しにやってみませんか」と提案があり、 全員でプランニングポーカーを実施しました!!!(iwateaさん、あざす)

現状は全員が同じPJで仕事をしているわけではないため、 全員で見積りをして計画するということをできていませんでした。トホホ

実践の結果、各々の見積りの感覚が言語化され、さらにお互いのことを知り合えましたし、 実際のPJの一部分を題材にプランニングポーカーを行ったので、 そのPJにも多少何ともいい影響があったのではと思います!!!

以前ブログでも書いたのですが、循環は非常に大事で、 インプットした内容はアウトプットしてやっと血肉になるので、 早いサイクルでそれができる組織・チームにしていきたいです!

aimstogeek.hatenablog.com

まとめ

「形成期」は『コミュニケーションと情報の「量」が重要』ということで、 チームの現状から「輪読会」をやると決めましたが、あくまで輪読会は手段の1つです!

チームの状態に応じて適切な手段を取れば良いと思います!

ただですね、輪読会は「なにより楽しい*1」!

また普段の業務では見えない個人の考えが言語化されるので、輪読会はオススメです。

では良いエンジニアリングライフを!


\\『真のユーザーファーストでマーケットを創造する』「ありがとう」で溢れる仲間を募集中です!! // 919.jp

おまけ

f:id:aimstogeek:20200221114517j:plain
輪読会のようす。フルーツパーラーさん、盗撮あざっす!

*1:アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~」を読んだ方であれば、なぜこれを言っているかおわかりですよね。笑

「すみません」よりも「ありがとう」が溢れるチーム

どーも、ご無沙汰しております!
シニアマネージャーのnakayanです!!

僕は、昔から個人的に意識していることがあります。
それは、不要な「すみません」を使わずにできる限り「ありがとう」と発することです。
「すみません」とパッと出てしまう場面もまだまだ多いのですが・・・

日本人の美徳でもあるのかもしれませんが、謝罪ではない場面でも「すみません」と言ってしまうことがすごく多いように思います。

  • エレベーターの扉を開けてもらっていて「すみません」
  • わざわざご足労いただいて「すみません」
  • 物を落としてしまったときに、拾っていただいて「すみません」

などなど。
おそらく「恐縮です」という意味合いで「すみません」が多用されていると思うのですが、自分を謙遜して過度に下げてしまう必要はないと思いますし、言われる相手も「すみません」と言われるより「ありがとう」と明るく笑顔で言われた方が嬉しいと思うんですよね。

厳密に同じ意味合いで捉えることは難しいですが、英語で考えた場合、たぶん「Sorry」ではなく「Thank you」って言いますよね?

お互いに前向きな感情になれる「ありがとう」でいっぱいにできると、みんながハッピーになるんじゃないでしょうか。

というわけで、2019年末にチーム内で感謝カードを贈り合うという取り組みを行いました!!!

年末特別企画!!
今年一年の感謝をメッセージカードで贈り合おう!!!

メンバーからの発案で始まったこの企画、nakayanは成り行きを「ほぇ~」「ほげ~」と見守っておりました。 前職で感謝メッセージカードをもらっているメンバーもいて、やっぱりもらうと嬉しいし、今でも大切に取ってある。と。

「いいね!」

企画としては紆余曲折ありまして、 「書くハードルは下げたいよねー」 「やっぱり手書きよりWeb上の方が良いのかなー」

f:id:aimstogeek:20200214115924p:plain
f:id:aimstogeek:20200214115954p:plain
f:id:aimstogeek:20200214120017p:plain

結果、(手書きでメッセージカードを)
書きましょう!!!!!!

実際の様子

企画メンバーから、全員に企画の意図とメッセージ内容のヒントを定義して説明してくれました。

こんな内容のメッセージを贈ろう!!
↓↓↓

  • ナイス! ⇒ すばらしかったこと、ほめたいこと。
  • サンキュー! ⇒ お礼を伝えたいこと
  • コングラッチュレーション ⇒ お祝いをいいたいこと
  • ベーリームービング! ⇒ その人の行いで、感動したこと

メッセージは匿名でもOK!! 宛名はカードの裏面に記載。

クリスマス後の12/26を提出締切として、投函されたメッセージカードは、最終営業日の12/27に企画メンバーが宛名別に仕分けをして各メンバーに届けてくれました。

f:id:aimstogeek:20200214122345p:plain
年賀状の仕分けみたい

たくさんの感謝メッセージが、たくさんのメンバーに届けられました!!! 誰がどんなメッセージを受け取ったのか、nakayanは知るよしもありません。。。

nakayanがもらったメッセージカードは、こんな感じです。(まだ誰にも見せてない)

f:id:aimstogeek:20200214122716j:plain
nakayanがもらった感謝カード

やっぱり感謝を伝えてもらうのは嬉しいし、形として残るのもすごく嬉しいものですね!! これは宝物として大切にしまっておこうと思います。

さいごに

僕にはもうすぐ3歳になる息子がいるのですが、きちんと「ありがとう」と「ごめんなさい」が言えるということをとても大切にしています。

子供にしっかりと、人として当たり前のことができている姿を見せられるように気を引き締めていきます。

悪いことをしたときは「ごめんなさい」。
恐縮の「すみません」より感謝の「ありがとう」。
一人ひとりがたくさんの「ありがとう」で溢れる人生を送れたら、こんなに素敵なことはないと思います。

手書きのメッセージだとあまり書いてくれないんじゃないかとか、そんなことは杞憂でした。

今回の企画を通して、みんなの想いがこもった沢山の感謝メッセージがやり取りされたこと、普段の何気ない行動や取り組みをしっかりと見てくれている人がいること、その行動が誰かの役に立っているんだと知ることができて自信と確信に変わり、さらに良い影響を広げられていくという、とても有意義な取り組みだったと思います。

僕自身も、日々周りの人に感謝し、自分の行動一つ一つが誰かの為になっていると信じて2020年も頑張っていきたいと思います!!


\\『真のユーザーファーストでマーケットを創造する』「ありがとう」で溢れる仲間を募集中です!! // 919.jp