Site Reliability Engineering #5

5章トイルの撲滅

通常運用中のシステムに人手が必要なら、それはバグだ。 通常の定義は、システムの成長と共に変化する。

-----Carla Geisser、Google SRE

5.1 トイルの定義

トイルは「労苦」を表します。 プロダクションサービスの動作に関係する作業で手作業で繰り返され、長期的な価値を持たず作業量がサービスの成長に比例する傾向を持つ。 自動化することが可能な作業である。 トイルの度合いが高い仕事は次の特徴がある

  • 手作業
  • 繰り返される
  • 自動化できる
  • 戦術的である
  • 長期的な価値を持たない
  • サービスの成長に対してO(n)

トイルと混同されやすい作業にオーバーヘッドがある。 管理上の雑務でプロダクションサービスの稼働に結びつかない仕事を含む、次のものである。

  • チームミーティング
  • ゴールの設定と評価
  • スニペット
  • 人事の事務作業

5.2 トイルは少ない方が良い理由

GoogleのSREは作業時間の50%以上を将来のトイル削減、またはサービスに機能を追加する作業に確保するべきである。 トイルの作業は50%以下に抑える目標がある。

トイルの作業を50%にするのはトイルに目を向けなければ自然に増えていき、全員の時間を100%トイルで埋め尽くす傾向があるためである。

トイルの下限も設定しており、6人が6週間ごとに2習慣のオンコールシフトと割り込み対応にあてる場合、潜在的なトイルの下限は

 \frac{2}{6} = 33 \%

になる。

5.3 エンジニアリングであるための条件

5.1, 5.2をまとめると典型的なSREの活動は次の分類に沿う。

  • ソフトウェア・エンジニアリング
    • コードの作成や修正を含む作業
  • システムエンジニアリング
    • プロダクションシステムの設定、システムのドキュメント作成が含まれ、1回の作業で改善効果が持続する方法で行われる。
  • といる
  • オーバーヘッド

5.4 トイルは常に悪なのか?

トイルは常に悪いものではなく、エンジニアリリングに関わる全ての職務にとって多少のトイルは避けられないものである。 トイルが大量になった場合、次の面で有害になる。

  • キャリアの停滞
  • モラルの低下
  • 混乱の発生
  • 進捗速度の低下
  • 習慣づけ
  • 摩擦の発生
  • 信義違反

5.5 まとめ

イノベーションを増やし、トイルを減らそう。

「入門 監視」を読んで

監視の原則やデザインパターンを体系だって学ぶことで、自己流の監視のアンラーニングを目的に読みました。

www.oreilly.co.jp

本書は監視の原則から始まり、知りたいことが体系立って書かれていました。

  • 監視の定石であるデザインパターン
  • 障害に対する適切なアラートのコントロール方法
  • 統計的なデータの扱い方の特徴と注意点
    • 平均値 vs 中央値
    • パーセンタイル
  • サービスの成長に伴う監視システムの内製/SaaSの選定、戦略
  • ビジネスKPIに紐づく監視戦略と監視項目、各社の具体例
  • ネットワーク〜Linuxカーネル〜アプリの技術スタックと監視内容
  • セキュリティ監視

特にIaaSの技術スタックに関する知識に偏りがちだった自分にとって、監視SaaSを組み合わせたメリットやデザインパターンはインプットになりました。

並行して読んでいるSite Reliability Engineeringが抽象度高く書かれており、具体例を補うものとして本書はとても役に立ちました。

Site Reliability Engineering #4

4章サービスレベル目標

ユーザに対するサービスレベルを定義する。 定義するものはサービスレベル目標(SLI)サービスレベル目標(SLO)サービスレベルアグリメーメント(SLA)になる。

4.1 サービスレベルに関する用語

  • SLI
    • 提供されているサービスの性質に応じて定義される
    • リクエストのレイテンシ、エラー率、システムスループット。可用性、耐久性
  • SLO

    • SLIで計測されるサービスレベルのターゲット、または範囲を指す
    • ターゲットの場合は  SLI \leqq ターゲット の関係が成り立つ状態を目指す
    • 範囲の場合は  下限 \leqq SLI  \leqq 上限 の関係が成り立つ状態を目指す
    • ユーザの動向によって決まるメトリクス(ex. QPS)をSLOにすることは現実的ではない
  • SLA

    • ユーザとの間で結ぶ契約のこと
    • SLOを満たした場合/満たさなかった場合の規定を含む
    • 「SLOを満たさなかったときにはどうなるのか?」から分かる

SLOにはユーザの関心事を反映させ公表して期待を設定する。 期待を設定することでユーザのサービスに対する独自の期待や過剰な信頼を取り除くことができる。

4.2 指標の実際

提供するサービスの性質によってSLIを選択する。

指標の収集結果を分布で捉えると、スパイクといった5%が全体の95%に影響を与える動作を見つけられる。 収集結果を算術平均で捉えると埋もれてしまう。

4.3 目標の実際

サービスの性質によって決まり、パーセンタイルを活用する。

  • パフォーマンスカーブが重要な場合

    • Get RPC呼び出しの90%が1m以下で完了すること
    • Get RPC呼び出しの99%が10m以下で完了すること
    • Get RPC呼び出しの99.9%が100m以下で完了すること
  • スループット、レイテンシそれぞれが気なるユーザがいる場合

    • スループットタイプのクライアントのSet RPC呼び出しの95%が、1s以下で完了すること
    • レイテンシタイプのクライアントの、1kB以下のペイロードを持つSet RPC呼び出しの99%が、10ms以下で完結すること

SLOを常に満たし続けるのはデプロイ頻度の低下を招き、高価で過剰に保守的なソリューションが必要になることに繋がる。

SLOは経営に関わる次の制約の中で満たす必要があり、議論する必要がある。

  • スタッフ、市場投入までの期間、利用できるハードウェア、資金

現実的な期待をユーザのために設定するには安全マージンを確保する、過剰達成を避けて計画的なサービス停止を導入するなどができる。

4.4 アグリーメントの実際

SLAはビジネス、法務のチームが規定やペナルティを選択できるようにする。 顧客の属性が幅広いほどSLAの変更削除が難しくなるため、開示する内容は控えめにする。

Site Reliability Engineering #3

第Ⅱ部 原則

トイルを撲滅することはSREにとって最も重要なタスクの一つである。 トイルの定義は日常的に繰り返される価値を生み出さない、サービスの成長に比例してスケールする運用作業である。

3章リスクの受容

Googleは過度の信頼性がプロダクトの提供速度の鈍化、過剰なコストの増大を生むと考えている。

  • 99%の信頼性をもつスマートフォンを使っているユーザに、サービスが99.99%の場合と99.999%の場合で違いは分からない

3.1 リスクの管理

システムの信頼性とコストの関係は比例ではなく、次の要因によってコストが100倍になることもある。

  • 設備の冗長化による、定期/予定外のメンテナンスコスト
  • ユーザに見えない、リスクを減らすためのシステムや機能の構築

SREはサービスの信頼性を管理する要因としてリスク管理を重視している。

3.2 サービスリスクの計測

システムの特徴を表す客観的なメトリクスを見出し、パフォーマンスの評価や改善、劣化の追跡ができるようになる。

メトリクスの例として次の指標が挙げられる。

  • 計画外の停止時間
    •  可用性 = \frac{稼働時間}{稼働時間 + 停止時間}
    • 可用性を99.99%か、99.99999%を目指すかが明快

ただし、googleはグローバルに分散したサービスなのでどこかの地域がレスポンスを返すため常に稼働中に見えてしまう。

代わりにリクエスト成功率を可用性として定義する。

  • 可用性 = 成功したリクエスト数 / 総リクエスト数

このメリットはユーザに直接見えないバックエンドのシステムに対しても同一のメトリクスを適用しやすくなる。 バッチシステムの場合は成功したレコード数、失敗したレコード数に置き換えて計算できる。

3.3 サービスのリスク許容度

多くの場合、アプリケーションのビジネスを担当するプロダクトマネージャーがいて、サービスの信頼性を議論する。 リスク許容度を議論するにあたって次の要素を元に検討する。

  • 可用性のレベル
    • サービスの性質、競合他社のレベル、BtoB、BtoCかなど
  • 障害の種類によるサービスへの影響の違い
    • ビジネスの時間帯に稼働していれば済むのか
  • リスク曲線上サービスを伸ばすことが優先されるか(機能開発速度が重視されるか)
    • 可用性を1桁上げた場合の収益と追加コスト
  • 考慮すべきその他重要メトリクス
    • ユーザの体感速度に応じたレイテンシの設定ができたら、コストを抑制できる

インフラストラクチャの場合はコンシューマと違い異なる特性を持つ多様なクライアントの要求が存在する。

  • 可用性のレベル
    • 低レイテンシ高可用性のサービス vs スループット重視のサービス
    • 同一インフラで実現することはコストが掛かりすぎるので、インフラを分割し、独立したレベルのサービスとして扱う。

インフラストラクチャは明示的に示したサービスレベルでサービスを提供することで、クライアント側がリスクとコストの適切なトレードオフを選択できる。

3.4 エラーバジェット

プロダクト開発者のパフォーマンス指標の開発速度と、SREのパフォーマンス指標の信頼性で評価すると緊張が生まれる。

評価の指標をエラーバジェットに置き換えてイノベーションと信頼性の適切なバランスを見出す。

エラーバジェットのルールは次の通り。

  • SLOに基づくエラーバジェットはプロダクトマネージャーが規定する
  • 稼働時間は第三者のモニタリングシステムで計測する
  • 目標と実績の差異を四半期の「損失可能な信頼性」という「予算」として管理する
  • 稼働時間がSLOを超えていれば、リリース可能とする。

エラーバジェットにより、プロダクトの開発チームが自己統制をするように促せるメリットがある。

参考

www.oreilly.co.jp

Site Reliability Engineering #2

2章 SREの観点から覽たGoogleのプロダクション環境

2.1 ハードウェア

Googleが設計した独自のデーターセンタ群から構成される

  • マシン
    • 一つのハードウェアまたは1つのVM
  • サーバ
    • サービスを実装しているソフトウェア

マシンは抽象化を施すことで、Googleクラスタオペレーティングシステムである「Borg」がプログラムの実行に必要なリソースの割当を受け持つ。

データセンター内のマシン間の通信を支えるためにJupiterと呼ばれる高速な仮想スイッチをGoogleは開発した。 JupiterはClosネットワーク装置であり、サーバ間で1.3Pbsの帯域幅をサポートする。 データーセンタ群はSDNにより地球規模のバックボーンネットワークで相互接続されている。

2.2 ハードウェアを「組織化」するシステムソフトウェア

Googleはスケーラビリティが高いソフトウェアによりハードウェアの障害によりユーザが影響を受けないようにしている

BorgはApache Mesosに似た分散クラスタオペレーティングシステムでジョブをクラスタのレベルで管理する - ジョブは同一タスクが1つ以上含まれることがある

Borgは障害ドメインを考慮して、タスクをマシンに配置する

  • #注釈 Borgはkubernetesの祖先にあたるシステム

タスクはHDPSに相当するストレージが利用でき、多くのレイヤーから構成される

ネットワークは世界中に分散配置されたシステムから、サービスのレイテンシを最小化する最も近いデータセンターにユーザを振り分ける。 ユーザの振り分けはGSLBで行われ、3つのレベルでロードバランスをする。

  • DNSリクエストへの地理的なロードバランス
  • ユーザサービスレベルのロードバランシング
  • RPCレベルのロードバランシング

2.3 他のシステムソフトウェア

これらに加えて非同期の合意形成に利用するPaxosプロトコルを持つChubbyというストレージがある。

モニタリングとアラートのためにBorgmanというインスタンスがサーバのメトリクスをスクレイプしている。

2.4 Googleのソフトウェアインフラストラクチャ

極度にマルチスレッド化、かつRPCを使った通信に揃えたソフトウェアアーキテクチャとすることで、分散化を容易にしている。

また、protocol buffersによりXMLと比較してサイズが最大1/10、最大100倍高速化している。

2.5 Googleの開発環境

Googleは単一の共有リポジトリを使って作業することで、次のメリットを得ている

  • プロジェクト外のコンポーネントに対して変更提案の”Changelist”をコンポーネントの所有者に贈り、メインラインに投入してもらうことができる
  • ”Changelist”が投入されるたびに依存関係を持つコンポーネントのテストが実行され、所有者が異常に素早く気がつける

2.6 シェークスピア:サンプルのサービス

具体例としてシェークスピアの全作品から単語を検索できるサービスの提供を考えてみる。

ユーザからのリクエストは次のデータフローに沿って処理される

  1. ユーザ -> DNS
  2. ユーザ -> GFE -> アプリケーションフロントエンド -> アプリケーションバックエンド -> Bigtable

ユーザのリクエスト受付からユーザへのレスポンス開始まで数百ミリ秒の時間で実現している。

Site Reliability Engineering #1

GoogleのSRE本を読み始めました。

www.oreilly.co.jp

1章イントロダクション

1.1 サービス管理へのシステム管理者のアプローチ

歴史的に企業はサービスを動作させるために既存のソフトウェアコンポーネントを組合わあせて動かせるシステム管理者を雇用してきた。 「シスアド」とも呼ばれるこの役割は開発者とは異なるスキルセットを持つので、「dev」と「ops」という別々のチームで分けられてきた。 シスアドという役割の利点はあり、業界では馴染みのあるパラダイムであり導入しやすいことである。

一方で「シスアド」とも呼ばれる役割を作ることで「dev」と「ops」の分断によりデメリットも生まれた

  • 直接的には手動運用が中心の「ops」チームはサービスの成長やトラフィックの増大によるシステムの運用負荷が増えるに連れてチームを拡大せざるを得なくなる
  • 間接的には「dev」と「ops」が異なるバックグラウンド、スキルセット、動機づけが異なるためプロダクト開発において安定性やリスクの捉え方について異なる解釈をして衝突が生まれる
    • 「好きなものを好きな時にローンチしたい」vs「いったん動いたシステムは一切変更したくない」

開発者は衝突を迂回する方法を学び、レビューの実効性が失われていく。

1.2 サービス管理者へのgoogleのアプローチ:サイトリライアビリティエンジニアリング

「dev」と「ops」の衝突に対して、Googleはサイトリライアビリティエンジニアリング(以降、SRE)チームはソフトウェアエンジニアを採用した SREはシスアドによって手作業で行われたであろう作業を自動化するシステムを構築する

  • SREのうち、60%はGoogleの標準的なソフトウェアエンジニア採用を経て雇用されたメンバである
  • 残り40%はソフトウェアエンジニアリングの必須条件を85%-99%満たす候補者である

  • 後者のほぼ必須条件を満たすメンバは、ソフトウェアエンジニア採用よりもエンジニアリングやUNIXシステム、ネットワーキングに関する専門知識をもつ

SREを採用していった結果、(a)手作業でタスクをこなすことにすぐ飽きる、(b)複雑でも手作業のシステムを自動化できるスキルをもつ、エンジニアのチームができた GoogleではすべてのSREに対してチケット、オンコール、手作業といった「運用」業務の合計を50%以下にする上限を設けた 上限を超えた場合、開発チームに差し戻したり新たな人員をチームに加えることができる システムの稼働、メンテンナンス、改善に必要なSREの人数はシステムのサイズに対して比例しない

1.3 SREの信条

サポートするサービスに対する一連の基本的な責任と、中核となる信条の尊重はすべてのSREチームに共通する。 SREチームはサービスの可用性、レイテンシ、パフォーマンス、効率性、変更管理、モニタリング、緊急対応、キャパシティプランニングに責任を負う。

  • 信条
    • SREが運用作業に書ける時間は50%
    • 「エラーバジェット」を導入して、サービスのSLOを下回らずに変更速度の最大化を追求する
      • 「サービス障害をゼロに」する目標から「エラーバジェット」に目標を変えることで、開発とSREの間で起きる構造的な競合を解決する
    • モニタリングは人間がメールを読んでアクションの必要性を判断するシステムは根本的な欠点を持つと考える
      • 効果的なモニタリングとして人間に即座のアクションを促すアラート、人間に日次のアクションを促すチケット、フォレンジックのっために記録するロギングの3要素を持つ
    • SREは70%のサービス障害は動作中のシステムへの変更によって生じることを発見し、自動化で以下を実現するベストプラクティスを得た
      • 漸進的なロールアウト
      • 高速かつ正確な問題の検出
      • 問題が生じたときの安全なロールバック

おうちkubernetesクラスタを作る

動機

kuberntesのSREを目指すにあたって、クラウドより下のレイヤの振る舞いから理解したかった。

ladicle.com

の記事を見て、NUCを買えばPCより安価にクラスタ構築ができることが分かったので構築した。

ハードウェア

MasterはC-planeなので安価なCeleron、WorkerはCore-i3にした。

  • Master
    • BOXNUC6CAYH
  • Worker
    • NUC8I3BEH

ソフトウェア

最新版でインストールした。 k8sの構築でdocker 19は未検証と出るが動作する。

  • docker 19.03.08
  • docker-compose 1.25.5
  • kubeadm v1.18.2

インストール

Ubuntuインストール

Ubuntu Live USBからインストール すればよい。 メニュー通りに進めると/rootの容量が小さくなってしまうので、/rootにすべて容量を割り当てる。 割り当てると次の通りになる。

$ df -h
Filesystem                         Size  Used Avail Use% Mounted on
udev                               3.8G     0  3.8G   0% /dev
tmpfs                              783M  1.9M  781M   1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv  218G   14G  194G   7% /
tmpfs                              3.9G     0  3.9G   0% /dev/shm
tmpfs                              5.0M     0  5.0M   0% /run/lock
tmpfs                              3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/loop0                          94M   94M     0 100% /snap/core/9066
/dev/loop1                          94M   94M     0 100% /snap/core/8935
/dev/sda2                          976M  213M  697M  24% /boot
/dev/sda1                          511M  6.1M  505M   2% /boot/efi
tmpfs                              783M     0  783M   0% /run/user/1000

kubernetesインストール

次の記事に従ってインストールできた。

qiita.com

ただし、Dockerはkubernetesの手順に従った。

kubernetes.io

コマンドはsudo権限を付けて実行すれば良い。

インストールできると次の通りになる。

$ kubectl get nodes
NAME      STATUS   ROLES    AGE   VERSION
master    Ready    master   18m   v1.18.2
worker1   Ready    <none>   17m   v1.18.2
worker2   Ready    <none>   17m   v1.18.2

ハマったところ

Masterでkubeadm resetに失敗する。

Masterのkubeadmバージョンが古く、一部のWorkerをjoinできなかった。 sudo kubeadm resetクラスタを作り直そうとしたが、etcdが操作を受け付けなかった様子。

$ sudo kubeadm reset
[reset] Reading configuration from the cluster...
[reset] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.
[reset] Are you sure you want to proceed? [y/N]: y
[preflight] Running pre-flight checks
[reset] Removing info for node "master" from the ConfigMap "kubeadm-config" in the "kube-system" Namespace
{"level":"warn","ts":"2020-05-02T20:40:05.390+0900","caller":"clientv3/retry_interceptor.go:61","msg":"retrying of unary invoker failed","target":"endpoint://client-23535e72-1c0d-4cd5-854d-174a71cdef2d/192.168.5.200:2379","attempt":0,"error":"rpc error: code = Unknown desc = etcdserver: re-configuration failed due to not enough started members"}

何らかの手違いで古いクラスタのetcdサーバが動いたままで操作を受け付けない状態になった。

$ ps aux | grep kube
root     27157  8.8  1.0 1558580 85108 ?       Ssl  20:52   0:01 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=systemd --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2 --resolv-conf=/run/systemd/resolve/resolv.conf
root     28131 23.6  0.4 145644 38652 ?        Ssl  20:52   0:03 kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true
root     28157 15.5  0.7 211216 63608 ?        Ssl  20:52   0:02 kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=127.0.0.1 --client-ca-file=/etc/kubernetes/pki/ca.crt --cluster-cidr=10.244.0.0/16 --cluster-name=kubernetes --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt --cluster-signing-key-file=/etc/kubernetes/pki/ca.key --controllers=*,bootstrapsigner,tokencleaner --kubeconfig=/etc/kubernetes/controller-manager.conf --leader-elect=true --node-cidr-mask-size=24 --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --root-ca-file=/etc/kubernetes/pki/ca.crt --service-account-private-key-file=/etc/kubernetes/pki/sa.key --service-cluster-ip-range=10.96.0.0/12 --use-service-account-credentials=true
root     28158 74.2  3.8 497060 308948 ?       Ssl  20:52   0:09 kube-apiserver --advertise-address=192.168.5.200 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key --etcd-servers=https://127.0.0.1:2379 --insecure-port=0 --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=6443 --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
root     28161 14.6  0.6 10612592 51264 ?      Ssl  20:52   0:01 etcd --advertise-client-urls=https://192.168.5.200:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/etcd --initial-advertise-peer-urls=https://192.168.5.200:2380 --initial-cluster=master=https://192.168.5.200:2380 --key-file=/etc/kubernetes/pki/etcd/server.key --listen-client-urls=https://127.0.0.1:2379,https://192.168.5.200:2379 --listen-metrics-urls=http://127.0.0.1:2381 --listen-peer-urls=https://192.168.5.200:2380 --name=master --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt --peer-client-cert-auth=true --peer-key-file=/etc/kubernetes/pki/etcd/peer.key --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt --snapshot-count=10000 --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
root     28837  4.0  0.3 141180 28908 ?        Ssl  20:53   0:00 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=master

すべてのコンポーネントをkill

ps aux | grep kube |grep -v "grep"| cut -d " " -f 6 | xargs -I{} sudo kill {}

をした後、kubeadmを再インストールして解決した。

docker daemonが起動に失敗する。

dockerのインストール手順通り実行して起動に失敗した。 原因はdaemon.jsonがフォーマットに従っていないことだった。

これは sudo権限で実行する時のクオーテーションをjsonの要素と同じにしていたことが問題だった。 シングルクォーテーションを使うことで解決。

sudo sh -c  'cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF'