[ 言語選択: English ]

1. 概要

Kubernetes(K8s)を構成するコンポーネントについて説明し、Kubernetesがどのように動作するのか解説します。

2. 参考資料

3. K8sの基本的な構成

公式ドキュメントには以下のような図が掲載されています。

k8sinternal.components of kubernetes

ノード(Node)と単純に書かれている場合には、Woker Node を指します。K8sシステムを構成する重要なapi-serverを構成するPodが動作するノードは Control Plane Node と呼び、区別することもあります。

このSCCPで利用するKubernetesクラスターでは、Control Planeの機能はWorker Node上で稼動していますので、Control Plane Nodeが独立しているわけではありません。

ノードの役割は次のコマンドで確認できます。

$ kubectl get node

api-serverが動き、システムの中心となるControl-Planeノードは、control-plane(v1.20.x以降)ロールが付いています。

以前はMasterノードと呼ばれていましたが、BLM運動の後、Kubernetesに限らず様々なプロジェクトのドキュメントから、master/slaveなどの用語は変更されています。

NAME       STATUS   ROLES           AGE      VERSION
u109ls01   Ready    control-plane   2y358d   v1.30.4
u109ls02   Ready    control-plane   2y358d   v1.30.4
u109ls03   Ready    <none>          2y358d   v1.30.4
u109ls04   Ready    <none>          2y358d   v1.30.4

3.1. ノード(Worker Node)の構成

Kubernetesのシステム上でコンテナを動作させるために、各ノードでは Container Runtime Interface (CRI)に準拠したDocker, containerd, CRI-O等のコンテナ・エンジンが動作していますが、その他にKubernetes固有のコンポーネントとして、kubeletkube-proxy が動作しています。

  • 仮想化ネットワーク

  • CRIに準拠したコンテナ・エンジン (Docker, containerd, CRI-O等)

  • kubelet プロセス

  • kube-proxy プロセス

このコンテナ・エンジンとkubelet, kube-proxyは必ず全てのノードで動作していて、後で説明するAPI-server(kube-apiserver)と協調し、ユーザーのPodなどを稼動させます。

3.2. 仮想化ネットワーク

公式ガイドの図には明記されていませんが、Kubernetesの中心となるのはネットワークの仮想化です。

初期の設計については、GitHubの中にドキュメントが残されています。

この文書では基本的な機能として4つが挙げられています。

  • (Pod内部の)コンテナ間通信

  • Pod間通信

  • ServiceとPod間通信

  • 外部と内部を繋ぐ通信

ゼミ室10の環境では、仮想化ネットワークを構築するために、Calicoを利用しています。

CalicoはLayer-3レベルで動作しBGPを利用して、各ノードに割り当てた/32なIPアドレスの所在を広報します。Flannelなど他のソリューションでは、VxLAN技術を利用してLayer-2での仮想化を提供しますが、パフォーマンス上の懸念があるためLayer-2である必要なければ、Layer-3を利用するCalicoがより良い選択とされています。

KubesprayのCalicoのデフォルト値がipipモードからvxlanモードに変更になりました。詳細は GitHubのissues を確認してください。

3.3. CRIに準拠したコンテナ・エンジン(Runtime)

Kubernetesの公式ドキュメントには、次の3つのCRI runtimesがリストに掲載されています。

  • containerd

  • CRI-O

  • Docker

CRIの説明に掲載されている Protobuf APIに、CRIに準拠したコンテナ・エンジンが実装するサービスが列挙されています。

基本的な役割は、service定義に記述されている以下のような機能を提供することです。

  • Runtime Service: コンテナ・イメージの取得・保存・削除

  • Image Service: コンテナ(pod)の起動・停止・削除

コンテナ・エンジンは定義されたサービスを提供することで、以下のような機能は選択したコンテナ・エンジンによって異なる振舞いとなる可能性があります。

  • コンテナ・イメージをどこからpullするか (Repository名を省略した時に、docker.ioを探すとは限らない)

  • コンテナ・イメージをどこに保存するか (/var/lib/docker/以下とは限らない)

  • 独自TSL/SSL CA局ファイルを使用する独自Registryへの接続設定の方法 (ca.keyファイルの配置先は異なる)

以前はDockerを使用していましたがv1.20以降は採用されなくなったため、現在ではcontainerdを使用しています。変更の背景については Don’t Panic: Kubernetes and Docker の公式ブログを参照してください。

3.3.1. kubelet

kubelet は Control-Planeの api-server (kube-apiserver) と連携し、pod(コンテナ)の)活動を管理します。

実際の各ノードでは、次のようなプロセスとして存在しています。

## ps auxww | grep kubelet の出力
root      151957  7.4  0.6 3357840 158592 ?      Ssl  May31 5958:14 /usr/local/bin/kubelet --logtostderr=true --v=2 --node-ip=192.168.100.51 --hostname-override=u109ls01 --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --config=/etc/kubernetes/kubelet-config.yaml --kubeconfig=/etc/kubernetes/kubelet.conf --pod-infra-container-image=k8s.gcr.io/pause:3.3 --runtime-cgroups=/systemd/system.slice --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin

3.3.2. kube-proxy

kube-proxy は、podの通信(ネットワーク)に関わる制御を行ないます。

各ノードではDockerのコンテナとして実行されています。

## docker ps | grep kube-proxy の出力
39ff1f5995bd    9d368f4517bb           "/usr/local/bin/kube…"  5 days ago    Up 5 days    k8s_kube-proxy_kube-proxy-pg7d4_kube-system_f95cad6f-482b-4c52-91f1-a6759cbe7a0b_2
622fa3ac83bd    k8s.gcr.io/pause:3.3   "/pause"                5 days ago    Up 5 days    k8s_POD_kube-proxy-pg7d4_kube-system_f95cad6f-482b-4c52-91f1-a6759cbe7a0b_2

kube-proxyはpodとして稼動しているので、kubectlからも動作の様子が確認できます。

$ kubectl -n kube-system get pod -l k8s-app=kube-proxy -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS G
ATES
kube-proxy-56j9f   1/1     Running   3          55d   192.168.100.54   u109ls04   <none>           <none>
kube-proxy-gt7gg   1/1     Running   2          55d   192.168.100.52   u109ls02   <none>           <none>
kube-proxy-hlkn8   1/1     Running   2          55d   192.168.100.53   u109ls03   <none>           <none>
kube-proxy-pg7d4   1/1     Running   2          55d   192.168.100.51   u109ls01   <none>           <none>
通信において、Load-Balancerが利用するIPアドレスは、各ノードのMACアドレスと関連付けられます。
その他のClusterIPなどのK8s内部で利用するIPアドレスは、ローカルMACアドレス(x[26ae]:xx:xx:xx:xx:xx)と
関連づけられ、いずれかのノードに割り当てられます。

## 外部から観察できるK8s内部ネットワークの様子
$ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.100.160          ether   00:1b:21:bc:0c:3a   C                     ens1
192.168.100.52           ether   00:1b:21:bc:0c:89   C                     ens1
192.168.100.53           ether   00:1b:21:bc:0c:3a   C                     ens1
192.168.100.54           ether   00:1b:21:bc:0c:3b   C                     ens1
...

## 内部から観察できるK8s内部ネットワークの様子
$ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.100.52           ether   00:1b:21:bc:0c:89   C                     enp1s0
192.168.100.53           ether   00:1b:21:bc:0c:3a   C                     enp1s0
192.168.100.54           ether   00:1b:21:bc:0c:3b   C                     enp1s0
10.233.113.131           ether   1a:73:20:a4:cd:b3   C                     cali1223486b3a4
10.233.113.140           ether   72:e9:69:66:14:dc   C                     cali79c5fc4a9e9
...

3.4. K8sシステム・コンポーネント (Control Plane)

Kubernetesのシステムの本体はControl Planeとして稼動しています。

  • api-server

  • kube-scheduler

  • etcd

  • kube-controller-manager (Controller Manager)

  • (オプション) Cloud Controller Manager

この内、etcd を除くコンポーネントは、Control Plane Node上のpodとしてコンテナ・エンジンの管理下で動作しています。

podとして稼動しているプロセスは、kubectlを利用して、どのサーバーで実行されているか確認することができます。

$ kubectl -n kube-system get pod
NAME                                      READY   STATUS    RESTARTS   AGE    IP               NODE       NOMINATED NODE   READINESS GATES
calico-kube-controllers-8b5ff5d58-rr4jp   1/1     Running   1          6d1h   192.168.100.54   u109ls04   <none>           <none>
calico-node-2cm5r                         1/1     Running   8          171d   192.168.100.53   u109ls03   <none>           <none>
calico-node-5x5pr                         1/1     Running   10         171d   192.168.100.51   u109ls01   <none>           <none>
calico-node-7v65s                         1/1     Running   10         171d   192.168.100.52   u109ls02   <none>           <none>
calico-node-l7hqn                         1/1     Running   7          171d   192.168.100.54   u109ls04   <none>           <none>
coredns-85967d65-7g7fb                    1/1     Running   0          6d1h   10.233.112.58    u109ls03   <none>           <none>
coredns-85967d65-hbtjj                    1/1     Running   3          55d    10.233.105.203   u109ls04   <none>           <none>
dns-autoscaler-5b7b5c9b6f-44jh8           1/1     Running   0          6d1h   10.233.105.9     u109ls04   <none>           <none>
kube-apiserver-u109ls01                   1/1     Running   20         110d   192.168.100.51   u109ls01   <none>           <none>
kube-apiserver-u109ls02                   1/1     Running   17         109d   192.168.100.52   u109ls02   <none>           <none>
kube-controller-manager-u109ls01          1/1     Running   8          171d   192.168.100.51   u109ls01   <none>           <none>
kube-controller-manager-u109ls02          1/1     Running   7          171d   192.168.100.52   u109ls02   <none>           <none>
kube-proxy-56j9f                          1/1     Running   3          55d    192.168.100.54   u109ls04   <none>           <none>
kube-proxy-gt7gg                          1/1     Running   2          55d    192.168.100.52   u109ls02   <none>           <none>
kube-proxy-hlkn8                          1/1     Running   2          55d    192.168.100.53   u109ls03   <none>           <none>
kube-proxy-pg7d4                          1/1     Running   2          55d    192.168.100.51   u109ls01   <none>           <none>
kube-scheduler-u109ls01                   1/1     Running   8          171d   192.168.100.51   u109ls01   <none>           <none>
kube-scheduler-u109ls02                   1/1     Running   7          171d   192.168.100.52   u109ls02   <none>           <none>
metrics-server-7c5f68c54d-zrtgl           2/2     Running   1          6d1h   10.233.105.248   u109ls04   <none>           <none>
nodelocaldns-9pz2w                        1/1     Running   7          171d   192.168.100.54   u109ls04   <none>           <none>
nodelocaldns-bzhwn                        1/1     Running   11         171d   192.168.100.51   u109ls01   <none>           <none>
nodelocaldns-nsgk7                        1/1     Running   12         171d   192.168.100.53   u109ls03   <none>           <none>
nodelocaldns-z44sj                        1/1     Running   12         171d   192.168.100.52   u109ls02   <none>           <none>

直接OSの管理下で稼動している etcd, kubelet はここには出てきません。

3.4.1. api-server

Kubernetes APIを提供し、各Nodeで動作するkubeletや、クライアントのkbuectlと通信をするサーバー・コンポーネントです。

受信した内容は、etcd に保存しています。

我々が直接通信する相手は api-server となります。この機能が失なわれると

3.4.2. kube-scheduler

Podオブジェクトが登録されると、そのPodをどのNodeで動作をするのかを決定するコンポーネントです。

3.4.3. etcd

etcd はオープンソースで開発されている、Key-Value型のNoSQL分散データベースです。 Kubernetes以外のプロジェクトでも多く利用されています。

etcdはpodではなく、Ubuntuではsystemdの管理下にあって、OSのサーバープロセスとして動作しています。

etcdctlというコマンドを利用して、etcdに保存されているデータがどのようなものか確認することができます。 サーバー上で実行する必要はありますが、namespace: metallb-system の情報は次のように確認することができます。

## 192.168.100.51-54上で実行
$ sudo etcdctl --endpoints https://192.168.100.51:2379 --cacert=/etc/ssl/etcd/ssl/ca.pem --cert=/etc/ssl/etcd/ssl/member-u109ls01.pem --key=/etc/ssl/etcd/ssl/member-u109ls01-key.pem get /registry/namespaces/metallb-system
/registry/namespaces/metallb-system
k8s

v1      Namespace

metallb-system"*$50ded0f3-600b-4433-a4ac-0adc17a50f192ZB
appmetallbb
0kubectl.kubernetes.io/last-applied-configurationx{"apiVersion":"v1","kind":"Namespace","metadata":{"annotatio
ns":{},"labels":{"app":"metallb"},"name":"metallb-system"}}
z
kubectl-client-side-applyUpdatevFieldsV1:
{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".
":{},"f:app":{}}},"f:status":{"f:phase":{}}}


kubernetes
Active"

3.4.4. kube-controller-manager

Controllerという仕組みで動作する kube-controller-manager というpodが動作しています。

Controllerオブジェクト自体は独自に作成することもできますが、このコンポーネントはKubernetesを稼動させるために必要なContorllerオブジェクトを複数実装しています。

公式ガイドの説明では、次の4つが挙げられていますが、DeploymentStatefulSet オブジェクトなどのコントロールも行ないます。

  • Node Controller - NodeのUp/Downを検出・通知するController

  • Replication Controller - Podの数を適切に保つためのController

  • EndPoint Controller - PodとServiceを結びつけるController

  • ServiceAccount/Token Controller - Namespaceが新規作成された際のデフォルトアカウント・APIアクセストークンを作成するController

最後のServiceAccount/Tokenは意識することはありませんが、Secretオブジェクトとして必要な情報が保存されています。

$ kubectl -n $(id -un) get secret

出力された先頭にある default-token-xxxxx が、ServiceAccount/Token Controllerが作成したSecretオブジェクトです。

NAME                  TYPE                                  DATA   AGE
default-token-4pmsl   kubernetes.io/service-account-token   3      109d
objectstore           Opaque                                4      109d
ssh-auhorized-keys    Opaque                                1      13d
ssh-host-keys         Opaque                                3      13d
$ kubectl -n $(id -un) get secret default-token-4pmsl -o yaml

次のように、token: …​kubernetes.io/service-account.name: default と関連する情報が登録されていることが分かります。

apiVersion: v1
data:
  ca.crt: ....
  namespace: eWFzdS1hYmU=
  token: ....
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: default
    kubernetes.io/service-account.uid: a37638b6-917e-41d0-b14b-7ba4eac7889c
  creationTimestamp: "2021-04-07T02:53:50Z"
  ....

kube-controller-managerのpodは次のような操作で確認することができます。

$ kubectl -n kube-system get pod -l component=kube-controller-manager -o wide
NAME                               READY   STATUS    RESTARTS   AGE    IP               NODE       NOMINATED N
ODE   READINESS GATES
kube-controller-manager-u109ls01   1/1     Running   8          170d   192.168.100.51   u109ls01   <none>           <none>
kube-controller-manager-u109ls02   1/1     Running   7          170d   192.168.100.52   u109ls02   <none>           <none>

3.5. この他のk8sコンポーネント

この他にDNSサーバーやCalicoの動作に必要なネットワーク・コントローラーなどが稼動しています。

4. K8sのカスタマイズ

Kubernetesシステムの中で、api-server は クライアントである kubelet や、Controllerオブジェクトを実装する kube-controller-manager と通信する中心的なシステムでした。 この api-server と通信することができれば、動作をカスタマイズすることができます。

Kubernetesは拡張のための柔軟な仕組みを持っていますが、ここでは次の仕組みについて説明していきます。

  • Controller

  • Custom Resource Definition (CRD)

CRDもControllerも個別の仕組みですが、通常は両方を利用して、システムを拡張します。

4.1. カスタマイズの事例

例えば、次のようにシステムの導入を簡単にするために、独自Operatorという仕組みを導入しているプロジェクトがあります。

GitHubを確認すると分かりますが、これらのProjectが利用するプログラミング言語は Go です。 KubernetesではAPIと通信するためのライブラリとして、*client-go を提供していて、その他のライブラリもGo言語をベースにしています。

Kubernetesに限らずシステム管理系のアプリケーションを作成するのであれば、スクリプト系言語だけでなく、C言語、Go言語は一通り学習することを目指してください。C言語は多くのオープンソースソフトウェアが利用しているので、コードを読める事、必要な修正を加えること、コンパイルできることなど、ができると役に立ちます。

Go言語は今回のようにカスタマイズをするために必要になる場合が今後は増えると思われます。その他にも、C言語やPerlのように作者によって作法が違う、OSが違ってコンパイルできない、本番環境にコピーしたら必要な共有ライブラリがなくて動かない、などの不具合は起こりにくくなります。そのため、今後はGo言語で作成されるアプリケーションが増えると思われますし、少し複雑なユーティリティプログラミングに向いています。

client-goではkubectlのような外部コマンドとして、api-serverと通信するサンプルなどが提供されています。

実際に独自Operatorを作成するには、client-go だけでは大変なので支援するツールとして、以下のようなフレームワークが提供されています。

これらのいずれかを選択して独自Operatorを、CRDController を利用して構築していきます。

4.2. Controller (コントローラー)

コントローラー・オブジェクトは、一つの Resource (リソース) を管理し、api-server と通信するプログラムです。

既に登録されているリソースには、対応するコントローラーが登録されていて、kube-controller-manager は Deployment, ReplicaSet などの基本的なオブジェクトを管理します。

最終的にはDockerコンテナとなりますが、以下のリンク先のようにRabbitMQ OperatorなどのDockerfileを確認すると、コンパイル(Build)された実行形式のファイルをコピーし、実行するだけのシンプルなDockerコンテナを作成しています。

## managerコマンドをコピーし、実行するだけのDockerfile本体の抜粋
FROM scratch

ARG GIT_COMMIT
LABEL GitCommit=$GIT_COMMIT

WORKDIR /
COPY --from=builder /workspace/manager .
COPY --from=etc-builder /etc/passwd /etc/group /etc/
COPY --from=etc-builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

USER 1000:1000

ENTRYPOINT ["/manager"]

4.2.1. コントローラーの基本的な動作

Controllerオブジェクトは、api-server と通信を行なうだけのコマンドですが、システムの状態を常に監視し、状態が変化した際にすばやく動作するための仕組みを備えています。

この仕組みはKubernetesとは別に各コントローラーが実装するもので、この共通の仕組みの作成をサポートするために各種のフレームワークが存在します。 またフレームワークは、api-server の負荷を下げるために、in-memory-cacheを利用した差分更新を提供するなど様々な工夫を備えています。

Reconciliation Loop と呼ばれる、その動きについては参考資料に挙げている ゼロから始めるKubernetes Controller を確認してください。

この動作を行なうために、1つのリソースと協調して動作するコントローラーは1つに限定されることになります。

4.3. Custom Resource Definition (CRD)

Kubernetesにはkubectlを通して任意のCRDが登録可能です。 サンプルとして、RabbitMQのOperatorがどのようなCRDを登録しているか確認します。

定義は長いので、lessなどにパイプで繋ぐか、ファイルに落してEditorでみるか、Webブラウザなどから確認しましょう。。

$ curl -L "https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml" | less
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.6.0
  labels:
    app.kubernetes.io/component: rabbitmq-operator
    app.kubernetes.io/name: rabbitmq-cluster-operator
    app.kubernetes.io/part-of: rabbitmq
  name: rabbitmqclusters.rabbitmq.com

ここまでで、KubeBuilderを利用して、controller-gen コマンドが生成していることが分かります。(controller-gen.kubebuilder.io/version: v0.6.0)

ここから先には、リソース定義の内容が続きます。

spec:
  group: rabbitmq.com
  names:
    categories:
    - all
    kind: RabbitmqCluster
    listKind: RabbitmqClusterList
    plural: rabbitmqclusters
    shortNames:
    - rmq
    singular: rabbitmqcluster
  scope: Namespaced
  versions:
  - additionalPrinterColumns:
    - jsonPath: .status.conditions[?(@.type == 'AllReplicasReady')].status
      name: AllReplicasReady
      type: string
    - jsonPath: .status.conditions[?(@.type == 'ReconcileSuccess')].status
      name: ReconcileSuccess
      type: string
    - jsonPath: .metadata.creationTimestamp
      name: Age
      type: date
    name: v1beta1
    schema:
      openAPIV3Schema:
        ....

ここで、kind: RabbitmqCluster の部分で、新しいRabbitmqClusterリソースの定義であることが分かります。

ツールが生成したファイルを読み込むのはなかなか大変ですが、--- に注目すると、このファイルは次の定義を含んでいます。

  • Namespace

  • CustomResourceDefinition

  • ServiceAccount

  • Role

  • ClusterRole

  • RoleBinding

  • ClusterRoleBinding

  • Deployment

このファイルを kubectl apply -f で適用しているので、結果を確認します。

$ kubectl -n rabbitmq-system get all

このコマンドの実行結果は次のようになります。

NAME                                             READY   STATUS    RESTARTS   AGE
pod/rabbitmq-cluster-operator-5b4b795998-sfxmm   1/1     Running   0          9m32s

NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/rabbitmq-cluster-operator   1/1     1            1           9m32s

NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/rabbitmq-cluster-operator-5b4b795998   1         1         1       9m32s
Error from server (Forbidden): rabbitmqclusters.rabbitmq.com is forbidden: User "yasu-abe@u-aizu.ac.jp" cannot list
resource "rabbitmqclusters" in API group "rabbitmq.com" in the namespace "rabbitmq-system"

エラーがでていますが、管理者権限でも表示される内容に変化はないため、いまは無視します。

登録したCRDに対応したRabbitmqClusterオブジェクトを作成するために、次のようなYAMLファイルを受け付けてくれるようになります。

apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: definition
spec:
  replicas: 3
  persistence:
    storageClassName: rook-ceph-block
    storage: 20Gi
  service:
    type: LoadBalancer
    annotations:
      metallb.universe.tf/address-pool: rabbitmq-pool

このファイルを kubectl -n rabbitmq-system apply -f した後に、管理者権限で状態を確認すると次のような表示になります。

$ kubectl -n rabbitmq-system get all
NAME                                             READY   STATUS     RESTARTS   AGE
pod/definition-server-0                          0/1     Init:0/1   0          30s
pod/definition-server-1                          0/1     Init:0/1   0          30s
pod/definition-server-2                          0/1     Init:0/1   0          29s
pod/rabbitmq-cluster-operator-5b4b795998-sfxmm   1/1     Running    0          29m

NAME                       TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)                                          AGE
service/definition         LoadBalancer   10.233.53.4   <pending>     5672:32533/TCP,15672:30224/TCP,15692:32648/TCP   31s
service/definition-nodes   ClusterIP      None          <none>        4369/TCP,25672/TCP                               32s

NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/rabbitmq-cluster-operator   1/1     1            1           29m

NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/rabbitmq-cluster-operator-5b4b795998   1         1         1       29m

NAME                                 READY   AGE
statefulset.apps/definition-server   0/3     30s

NAME                                      ALLREPLICASREADY   RECONCILESUCCESS   AGE
rabbitmqcluster.rabbitmq.com/definition   False              Unknown            32s

これは、登録した RabbitMQCluster オブジェクトを受けて、Operatorが必要な StatefulSetService オブジェクトを登録した様子を表わしています。

実際にどのように動作しているかの確認は、GitHub上のコードを確認するのが一番の近道です。

以上