NWがおせえ？ 証拠を見せろッ！！！！

# まえがき

どもです。Gzockです。
[株式会社ビットキー 情シス Advent Calendar 2023](https://qiita.com/advent-calendar/2023/bitkey-corp-eng)の4日目の記事です。

アドベントカレンダーの記事って本名を名乗るもんなんすかね？

何かうちのアドベントカレンダーの記事はみんな本名を名乗ってる気がする。

インターネット老人会に所属している私は本名を明かすのに非常に抵抗があります。このままでいきます。

# そもそもの話

- 私は情シス所属ではありません
- 普段はIoTの開発やってます
    - [2023 AWS Summit Tokyoで弊社が登壇](https://speakerdeck.com/bitkey/yuzati-yan-woxiang-shang-saserukagiha-fen-duan-nojie-xiao-jue-dui-nizhi-merarenaisumatorotukuwoxi-usisutemunoli-ce)させてもらいましたが、その中で言及しているプラットフォームを担当しています
    - その他、登壇資料を見てもらえるとどんなヤツか大体わかると思います
        - [ビットキーのIoT基盤におけるAWS IoT Rule Action活用](https://www.slideshare.net/ryosasaki376695/iotaws-iot-rule-action)
        - [Okta Workflowsによるノーコード業務改善](https://www.slideshare.net/ryosasaki376695/okta-x-jamf-okta-workflows-jamf-apimacpdf)
        - [たまにはオンプレもいいぞ？GKEとArmadillo IoTで実現するハイブリッドクラウド](https://www.slideshare.net/ryosasaki376695/cloud-native-days-tokyo-2022-gke-armadillo-iot)
        - [Datadog Agent on CloudRunによるGCPトレーサビリティの向上](https://www.slideshare.net/ryosasaki376695/datadog-agent-on-cloudrungcp)
- なのになんで情シスアドベントカレンダーの2番手やってんの？って？
- わかりません, ボス(@h_r_w_t_r)に聞いてください
    - ちょっとした経緯は上述のOkta WFsの記事で言及しています

# この記事は何？

- ネットワークが遅いって言われたこと？あるでしょ？あるはず
- ほんまか？NWが遅いってそれ主観要素が結構あるぞ
- 少なくとも俺は8.8.8.8へのショートパケットpingのRTTが10msを超えたら”おせえ”って思ってるぞ
- 後述するけれども、弊社ではNWが遅くて皆の不満がすごかった
- それを何とかしたかった
- 何とかするための前段記事

# 本題

- `全端末がWi-Fi環境` であり有線接続している端末は一部の特殊用途を除き存在しない
- `Meraki` によるクラウド管理型Wi-Fi環境を構築
- 東京オフィスに `20台超のアクセスポイント(AP)を敷設`
- その配下に `macOS端末が150台程度は存在`
- 基本的に大半の業務が何らかのSaaSに依存しており、 `ネットワークが生命線`
- ちなみに開発や検証の関係でiPhoneやiPad, Android, 弊社IoTプロダクトなども存在するため、 `実際のクライアント台数は数百台になる`
- そんな状況下で `弊社では多くの社員より”Wi-Fiがつながらない”, “ネットが遅い”などの声が多発`
- なんとかしたい！

# 課題

- 定性的な課題としては、 `”NWが遅くて皆の業務が滞りがちだから何とかしたい”`
- とはいえ、"なぜそうなっているのか？", "そもそも本当にNWは遅いのか？" を客観的指標により詳らかにする必要がある
- さらに、そこから分解した課題として以下を考えた
    - そもそも材料や証拠がなくてNWが遅い, 遅いにしてもどこが遅いかがわからない
    - 何を持ってNWがよくなったと判断するか？がわからない
- その2点を中心にさらに詳しく述べる

## 材料がない

- 悪いが、"NWが遅い！"  なんてぶっちゃけ主観的要素が入ってくる
- 本題のところに書いた通り”ネットが遅い”って言っても、その `”遅い”とは個々人の主観` による
- 回線速度測定をやってもらったところで、その数値だけ見せられても、ほぼ役に経たず非常に困る
- その人がその時点でどこに座っていて、どのAPにつながっていて、どの規格でつながっていて、何の操作をしていたのか？という情報が欲しいけど、どれもこれもほぼ手に入らない
- もちろんコアNW側であるMerakiのメトリクスは、ある程度こちらで情報を得ることができるが、それだけじゃ足りない
- 本当にその瞬間、その人のNWが遅かったかどうかなんてわからないし、何かコアNW側に問題があったかどうかもわからない
- わからなければ戦いようがない

## 何を持ってNWがよくなったと判断するか？

- 1点目の話と同じで主観要素がある以上、よくなったね！と言い切ることが難しい
- エンジニアたるもの何らかの数値, データでそれを `定量的に判断` できるようにしておきたい
- そもそもWi-Fiのパフォーマンス問題というのは原因が多岐に複合的に絡み合っていることが多い
- その場合、1つ1つ仮説を立ててアプローチしていくことになるわけだが、 `定量的に良い悪いを判断できなければ、その仮説立証をすることすらできない`
- なんかNW良くなったね！ → んなもん信じられるかあああああああああああああ

# [とはいえ] Merakiで色々メトリクス見れるでしょ？それじゃダメ？

- 参考にはなるが、それだけでは全然足りない
- コアNWとしてのパフォーマンスやらAPから見た云々は得ることができるが、クライアントから見たメトリクスは当然ながら取得できない
- CSMA/CAプロトコルを採用するWi-Fi環境の場合、クライアント側からするとコリジョン多発 → くっそ体感悪いという事態になっている可能性がある
    - これ自体はWi-FiのコアNW側からだとなかなか気づきづらい
- そのためMeraki側のメトリクスを見るだけでは根本原因を探しづらい
- 何より、NWが良くなった悪くなったの評価をMeraki側だけでは言い切ることができない
- 例えばMerakiのメトリクスを確認して、別にどこも輻輳してないとして、それでNWは良い！なんて言えるか？ → んなわけない

# 課題へのアプローチ

## 前提

- Merakiだけではどうにもならない
- macOSだけでもどうにもならない
- 社内端末にはJamfがインストールされている
- 弊社プロダクト開発側ではDatadogを主に利用し様々なサービスやプロダクトの監視と分析を行っている

## 方法

- macOSの`airport`コマンドによってWiFi関連メトリクスを取得しDatadogにメトリクスを集めるスクリプトを実装
- ↑をJamfのポリシーのスクリプト機能を使って各端末で定期的に実行
- Datadog側で収集したメトリクスを使ったダッシュボードを作成し、それを持って分析する

## ざっくりと

1. macOSに実装されているairportコマンドを用いるとでWi-Fi関連の情報を得る
2. コマンドを叩き、その出力結果をパースし、Datadogにカスタムメトリクスとしてプッシュする
3. さらにパフォーマンス測定としてpingである程度のペイロードを積んで何発か投げRTTを計測させる
    - これもまたDatadogにプッシュ

## airportコマンドとは？

- macOSに純正実装されている `Wi-Fi関連の操作が行えるツール` の1つ
- Wi-Fiの情報を取得するだけでなく、SSIDの切り替えやらそういう変更系もやれる
- 例)
    
    ```bash
    $ /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
         agrCtlRSSI: -63
         agrExtRSSI: 0
        agrCtlNoise: -97
        agrExtNoise: 0
              state: running
            op mode: station
         lastTxRate: 286
            maxRate: 229
    lastAssocStatus: 0
        802.11 auth: open
          link auth: wpa2-psk
              BSSID:
               SSID: YOUR_SSID
                MCS: 11
      guardInterval: 800
                NSS: 2
    ```
    

## Jamfのポリシーとは？

- 各macOSを何らかの条件によってグループ化し、それらに対して、どうあるべきか？を定義および強制させる機能
- この中で指定した任意のスクリプトを実行させることができる
  - 参考 : https://learn.jamf.com/ja-JP/bundle/jamf-pro-documentation-current/page/Scripts.html
- そんなに柔軟に色々できるわけではないが、今回のような用途では必要十分

# 集めたいメトリクス

| 項目 | 内容 |
| --- | --- |
| 最小RTT | 特定FQDNに対するRTTの最小値。最速どれぐらいだったのか？を知るために欲しい。 |
| 平均RTT | 特定FQDNに対するRTTの平均値。Wi-Fiの場合、割とジッターが大きくでることが多いので平均は重要。これが実際問題の体感につながりやすいはずなので。 |
| 最大RTT | 特定FQDNに対するRTTの最大値。これと最小値が大きく離れるようなら、ジッターが大きく、体感としてはかなり悪くなる。ずっと遅いというのも嫌だが、早いと遅いが中途半端に混在してると非常に不快。 |
| RSSI | クライアントから見た電波強度。RTTと併用して判断すると有用。つまり電波強度が良いのに平均RTTが悪いとなれば、それは電波問題の原因を一つ削ることができる。そういったことを判断できるようにするためにもクライアント側から見た値というのを得ておきたい |
| S/N比 | 昨今のオフィスではBLE接続デバイスが大量にある。あるいはキーボードとかマウスの2.4Ghz接続とか。そういうものの悪影響度合いを見るために必要。ただしこれはMeraki側からでも得ることはできる。 |
| 送信(TX)レート | ただの目安。マジで目安。別になくてもいいと思う。ただこの値自体はmacOS側で自動計測した値になるので、その時間にOS的な判断としてはどうだったのか？という情報自体は参考にはなるかなと思っている。 |

## スクリプト

- 今回はbashで実装
- 別にzshでもいいし、macOSなのでpython3でもOK
- ただし、全然簡単なスクリプトなので、下手にpythonとかで実装するよりもシェルスクリプトぐらいのほうが運用保守コストは安い気がしてこっちにした
    - ただ後になって思ったけどゴリゴリに文字列抽出してるのでpythonに頼ったほうが可読性は良いかもしれない

```bash
#!/bin/bash

set -eo pipefail

# RTT計測のときに使用する対象のFQDN
readonly TARGET_HOST="your_target_host"

# コマンドパスの定義, OS verによって異なるかもしれん
readonly _AIRPORT_CMD="/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport"

# airportコマンドから各種必要な情報を抽出する
# ここで抽出した情報はメトリクスのメタデータとして使う -> こうすることで後々そのメタデータ = タグを使った分析が非常に捗る
readonly _SSID=$(${_AIRPORT_CMD} -I | grep " SSID:" | cut -d ":" -f 2 | sed -e "s/^ //g")
readonly _CHANNEL=$(${_AIRPORT_CMD} -I | grep " channel:" | cut -d ":" -f 2 | sed -e "s/^ //g")

# RTT計測のためのパラメーター定義
# ペイロードをギリギリまで積むことで実際のユースケースに近づける
# 値は各環境によって異なるので事前にフラグメントしないMTUを調べたほうがいい
readonly _PAYLOAD_SIZE=1432
readonly _COUNT=10
readonly _TIMEOUT=10

# Datadogにプッシュするときのパラメーター
# APIキーは事前にDatadog側で発行させておくこと
# ここではスクリプトにハードコードした結果、各端末側に露出されてしまうので注意
# 諸々の前提条件からうちでは問題ないと判断しているが、そこは各社で判断してほしい
# 必要に応じて実装変更すべし
readonly _DD_API_KEY="your_api_key"
readonly _USER=${USER:-${3}}
readonly _HOSTNAME=$(scutil --get ComputerName)
readonly _DATE=$(date +%s)

# メトリクス生成

# 予め設定したparamsを使ってpingを打ってその結果を得る
readonly result=$(ping TARGET_HOST -s ${_PAYLOAD_SIZE} -c ${_COUNT} -t ${_TIMEOUT} | grep "min.avg.max.stddev" | sed -e "s/.*min.avg.max.stddev...\(.*\) ms/\1/g")

# pingの結果だけだとまだメトリクスとしては不十分な形式なので、そこからさらにパースしてmin, avg, maxの値を取り出す
readonly rtt_min=$(echo ${result} | cut -d "/" -f 1)
readonly rtt_avg=$(echo ${result} | cut -d "/" -f 2)
readonly rtt_max=$(echo ${result} | cut -d "/" -f 3)

# ここから先はairportコマンドから取得した情報をそのまま使う形で使ってメトリクスを生成する
readonly txRate=$(${_AIRPORT_CMD} -I | grep " lastTxRate:" | cut -d ":" -f 2 | sed -e "s/^ //g")
readonly rssi=$(${_AIRPORT_CMD} -I | grep " agrCtlRSSI:" | cut -d ":" -f 2 | sed -e "s/^ //g")
readonly noise=$(${_AIRPORT_CMD} -I | grep " agrCtlNoise:" | cut -d ":" -f 2 | sed -e "s/^ //g")
readonly snr=$((${rssi} - ${noise}))

# Datadogにプッシュさせる本体
for metric in rtt_min rtt_avg rtt_max txRate snr; do
  curl  -i -X POST "https://api.datadoghq.com/api/v2/series" \
  -H "Content-type: application/json" \
  -H "DD-API-KEY: ${_DD_API_KEY}" \
  -d @- << EOF
  {
    "series": [
      {
        "metric": "office.network.${metric/_/.}",
        "type": 1,
        "points": [
          {
            "timestamp": ${_DATE},
            "value": ${!metric}
          }
        ],
        "tags": [
          "host:${_HOSTNAME}",
          "user:${_USER}",
          "ssid:${_SSID}",
          "channel:${_CHANNEL}"
        ]
      }
    ]
  }
EOF
echo

done
```

## Jamf側の作業

### 流れ

1. スクリプトをJamfに登録
2. 登録したスクリプトを用いる形でポリシーを作成

おわり。めちゃんこ楽だね

### スクリプト登録

- 設定 > コンピュータ管理 > スクリプト > 新規
    - 必要に応じてオプション設定で任意の引数を渡せるので活用しても良いかも

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/c040eedf-5a99-01ea-93c8-6b554cf23eb7.png)

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/b6ed3eea-409d-a0a8-40d3-82853f9ab316.png)

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/82a4f278-5ad6-f110-e207-618f22a64823.png)


    

### ポリシー登録

- コンピュータ > ポリシー から新規作成して適宜パラメーターを設定する
- ポイントとしてはトリガーとして、`Recuring Check-in`を指定し、実行頻度として `Ongoing`を指定すること
    - これで最短15分おきにスクリプトを実行できる
    - つまり15分おきにメトリクスを集められるということ
    - 15分というのはJamfの仕様によるもの
- あとはメトリクスを集めたい対象の端末が含まれるようにScopeを設定すればOK

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/f4bc0701-b631-3e65-9050-a4b9cb753eb5.png)

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/c24e3511-9007-3a26-9985-05699b2069b5.png)

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/0939f67c-a271-adb4-e4b4-e498caacf256.png)


# Datadog

- 以下のようにメトリクスを集められる
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/c4f07b01-ec6e-33f4-4011-29b842870972.png)
    
- 収集したメトリクスを使ってダッシュボードを組むとこんな感じ
    - 本当はもっと大きなダッシュボードなのだけれど、全量お見せるのはさすがに危ういのでトップの重要指標の部分だけ
        
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80163/e6670bb7-bfa1-a01a-3856-5086bd2aad84.png)

        

# その他

## Meraki側のメトリクス収集

- これとは別にMeraki側でもメトリクスをMerakiに集める
    - もしかしたらアドベントカレンダーで記事書くかもしれない
- そうすることでコアNW側とクライアント側の両方のメトリクスを使って原因探索を行うことができる

## Datadogじゃなきゃダメ？

- んなこたーない
- 元々プロダクト開発側でDatadogを使っており、私がそっち側の人間なので採用しただけ
- 昨今のオブザーバビリティSaaSはどれもAPIを用意しているので、同じアプローチが使える
    - プッシュする部分の実装がそれぞれ異なるだけ
- 大事なのは `”仮説検証のために定量的な評価を行える環境を整える”` ことであり、その具体実装は自社の事情に合わせて選択すれば良いと思う

## 定期的に実行したら端末重くならない？大丈夫？

- 単純に1発コマンド叩いて結果をパースしているだけなので非常に軽量
- RTT計測もpingを数発打ってるだけなのでこれも軽量
- よってこれらのスクリプトが動くだけでユーザーレベルで何か影響がでることはまずない

# 結果

- `全時間帯の全社員のNW状況を可視化することができるようになった`
- 結果的に、 `どの時間帯にどの場所、どの社員がNWが悪くなっているか？を定量的かつファクトベースで判断することができるようになった` 
- Meraki側のメトリクスも併せることで、クライアント側のメトリクスが良い悪いときに、ではMerakiではどうなってたの？とかを簡単に判断することができるようになった
- これらのデータに基づいて、様々な対策をとっていけるようになった

# あとがき

- これはオフィスのWi-Fi環境の改善に関する”まずは”な内容の記事になる
- アドベントカレンダーの後半のほうでこれらのメトリクスを使ったWi-Fi改善の具体に迫った記事を投稿予定