Rancherコードリーディング入門(2/3)

Rancherコードリーディング入門(2/3)
前回の記事では、rancher/rancherレポジトリ直下のディレクトリやファイルについて、ざっくりどのような役割を持っているのか紹介しました。
pkgディレクトリ(https://github.com/rancher/rancher/tree/v2.1.5/pkg)にRancherのほとんどのロジックが含まれていることは前回の記事でおわかりいただけたと思います。
今回と次回の2回で、その中でも特に重要なpackage、Rancher Agentのロジックを含むpkg/agentや、Rancher APIのロジックを含むpkg/apiなどを紹介していきます。まず今回は、コードリーディング入門の連載2回目として、Rancher Agent(pkg/agent)、Rancher API(pkg/api)について詳しく紹介していきます。
2回目:Rancher Agent、Rancher APIを中心に紹介します
- rancher/rancherレポジトリの歩き方(2/3)
- Rancherのアプリケーションコード
- pkg/agent
- pkg/api
- Rancherのアプリケーションコード
3回目:Rancher Controller、Rancherの周辺レポジトリについて紹介します
- rancher/rancherレポジトリの歩き方(3/3)
- Rancherのアプリケーションコード
- pkg/controllers
- Rancherのアプリケーションコード
- rancherレポジトリ以外の重要なレポジトリについて
- rancher/types
- rancher/rke
- rancher/kontainer-engine
- rancher/norman
rancher/rancherレポジトリの歩き方(2/3)
pkgディレクトリ内のpackageを詳細に見ていく1つ目のトピックとして、Rancher Agentに関するロジックが含まれるpkg/agentから紹介します
pkg/agent
リスト1:pkg/agentのファイルとディレクトリ
pkg/agent/ ├── clean/ ├── cluster/ ├── main.go ├── main_windows.go └── node/
pkg/agentの説明
| ディレクトリ/ファイル | 説明 |
|---|---|
| clean/ | Rancher Agentのエントリポイントとなるmain.goは、単発で実行されるcleanupにも利用されるため、そのcleanupロジックが含まれる |
| cluster/ | Cluster Agent 特有の関数が含まれる |
| main.go | Rancher Agentのエントリポイントとなる |
| main_windows.go | WindowsをWorkerとする場合のエントリポイント |
| node/ | Node Agent 特有の関数が含まれる |
RancherのNode AgentとCluster Agentのエントリポイントになります。実は、Node AgentとCluster Agentは同じmain.goを利用しており、環境変数で次のように処理を分岐しています
リスト2:pkg/agent/main.go(の一部)
...
57
58 func isCluster() bool {
59 return os.Getenv("CATTLE_CLUSTER") == "true"
60 }
61
62 func getParams() (map[string]interface{}, error) {
63 if isCluster() {
64 return cluster.Params()
65 }
66 return node.Params(), nil
67 }
...
Agentの動作は、Cluster Agent、Node Agent問わずとてもシンプルで、大きく分けて2つのロジックが実装されていると理解できます。
以下にコードの一部を掲載しました。run関数は、main関数から呼ばれており、Rancher Agentのメインとなるロジックが定義されています。このロジックは、166~197行と199~215行の2つのロジックに分けられます。
166~197行目のロジックは、WebSocket Sessionが確立された後に呼び出され、定期的にrkenodeconfigclient.ConfigClientを呼び出しています。rkenodeconfigclient.ConfigClientは、/v3/connect/config APIを呼び出し、Nodeで動かすべきシステムコンテナのリストと作成すべきファイルを取得し、コンテナの実行、ファイルの作成を実施しています。173行目を見てわかるとおり、このロジックは、Rancher Node Agentのみで実行され、Cluster Agentの場合このロジックを実行しません。
199~215行目のロジックは、WebSocket Sessionを確立し、Proxy Serverとしてふるまうためのロジックが含まれています。remotedialer.ClientConnectにRancher Serverのエンドポイントと無名関数(func(proto, address string))を1つ渡しています。このremotedialer.ClientConnectは、WebSocket Sessionを確立した後に、Proxy Serverとしてふるまいますが、Proxy ServerでUnix SocketへのProxyをする際には、「/var/run/docker.sock」以外には、Proxyを許容しないように無名関数を指定し、Proxy要求の際のProtocolとAddressをチェックしています。Sessionが閉じられてしまった場合は、5秒ごとにリトライをするように設定されています。このロジックは、Rancher Node Agent、Cluster Agent両方で利用されています。
リスト3:pkg/agent/main.go(の一部)
139 func run() error {
... <省略>
165
166 onConnect := func(ctx context.Context) error {
167 connected()
168 connectConfig := fmt.Sprintf("https://%s/v3/connect/config", serverURL.Host)
169 if err := rkenodeconfigclient.ConfigClient(ctx, connectConfig, headers, writeCertsOnly); err != nil {
170 return err
171 }
172
173 if isCluster() {
174 return nil
175 }
176
177 if err := cleanup(context.Background()); err != nil {
178 return err
179 }
180
181 go func() {
182 logrus.Infof("Starting plan monitor")
183 for {
184 select {
185 case <-time.After(2 * time.Minute):
186 err := rkenodeconfigclient.ConfigClient(ctx, connectConfig, headers, writeCertsOnly)
187 if err != nil {
188 logrus.Errorf("failed to check plan: %v", err)
189 }
190 case <-ctx.Done():
191 return
192 }
193 }
194 }()
195
196 return nil
197 }
198
199 for {
200 wsURL := fmt.Sprintf("wss://%s/v3/connect", serverURL.Host)
201 if !isConnect() {
202 wsURL += "/register"
203 }
204 logrus.Infof("Connecting to %s with token %s", wsURL, token)
205 remotedialer.ClientConnect(wsURL, http.Header(headers), nil, func(proto, address string) bool {
206 switch proto {
207 case "tcp":
208 return true
209 case "unix":
210 return address == "/var/run/docker.sock"
211 }
212 return false
213 }, onConnect)
214 time.Sleep(5 * time.Second)
215 }
216 }
Rancher Agentに関するロジックは、これのみになります。ここを見てもわかりますが、Rancher Agentは基本的には、Proxy Serverの起動のみを実施し、それ以外のイベントはすべてRancher ServerのManagement ControllerかUser Controllerがハンドリングの責任を持ちます。
pkg/api
続いて紹介するのは、Rancher API関連のロジックが含まれるpkg/apiです。
リスト4:pkg/apiのファイルとディレクトリ
pkg/api/ ├── controllers/ ├── customization/ ├── server/ ├── server.go └── store/
pkg/apiの説明
| ディレクトリとファイル | 説明 |
|---|---|
| controllers/ | API Controllerの実装が含まれる |
| customization/ | Kubernetesリソースを保存する以外のAPIをManagement APIとして提供する際のロジック(action)が含まれる |
| server/ | Customer Resource Definitionの作成やRancher API Controllerの初期化など、Rancher APIに関わるすべての初期化を実施するロジックが含まれる |
| server.go | server/server.goに利用されるRancher APIに関する関数が含まれる |
| store/ | Rancherで扱う各リソースの保存方法を定義している。基本的にはKubernetesにリソースを保存するが、リソースの値によって保存先を切り替えたり、特定のフィールドを削除したりといった処理が定義されている |
/pkg/apiに含まれるロジックは、以下の4つに大別されます。
- Rancher API Controllerの実装(/pkg/api/controllers)
- Rancher APIの拡張(/pkg/api/customization)
- Rancher APIのバックエンドとなる保存先の設定(/pkg/api/store)
- Rancher APIの初期化(/pkg/api/server)
1つずつ見ていきましょう。
/pkg/api/controllers
Rancher API Controllerは、Rancher API Serverの設定/挙動を管理するためのRancherのControllerです。次のように6つのディレクトリが/pkg/api/controllersの配下に存在しており、1ディレクトリが1つのControllerにマッチします。
リスト5:pkg/api/controllers
pkg/api/controllers/ ├── dynamiclistener ├── dynamicschema ├── samlconfig ├── settings ├── usercontrollers └── whitelistproxy
6つのControllerがどのような役割を持っているのか1つずつ紹介します。
dynamiclistener
dynamiclistenerコントローラは、Rancher API ServerがどのProtocolでListeningするかを設定する役割を持っています。現在は、httpsとacmeをサポートしています。
dynamicschema
dynamicschemaコントローラは、API Serverが提供しているAPI Schemaを拡張、更新する役割を持っています。後ほど紹介しますが、Rancher APIは、Norman Frameworkをベースに実装されています。
Norman Frameworkでは、API Serverに対してAPI Schemaオブジェクトを登録することで、APIを拡張することできます。Rancherが利用しているCustomer Resource Definition(以下、CRD)の1つに、dynamicschemaリソースがあります。
このdynamicschemaリソースは、このAPI Schemaオブジェクトがシリアライズされたものになっており、このdynamicschemaコントローラが新しいdynamicschemaリソースを検知すると、該当のリソースからAPI Schemaオブジェクトを生成し、API Serverに設定します。
このようにdynamicschemaコントローラは、Rancher APIを拡張する役割を持っています。
samlconfig
samlconfigコントローラは、Rancher GUIの認証プロバイダの設定であるauthconfigリソースを監視し、SAMLを利用した認証プロバイダがEnableにされたら、SAML認証(Security Assertion Markup Language)で利用するエンドポイントの初期化を実施する役割を持ちます。
settings
settingsコントローラは、Rancherの設定を管理しています。これはコントローラのディレクトリ配下に配置されているものの、特定のリソースを監視し、何かアクションを起こすということはしません。
settingsコントローラは、Rancher Server起動時に、Rancher Serverコンテナの「CATTLE_<Setting項目名>」の環境変数を利用して(指定されていなかった場合はコード上で定義されているデフォルト値を利用します)Settingリソースを作成し、Rancher Serverの設定を管理しています。
起動後は、Rancher Serverの他のモジュール、コントローラが設定を参照する際に、このコントローラを経由して、Settingリソースの値を取得し、それを設定値として解釈します
usercontrollers
userscontrollersコントローラは、起動されると2つのgoroutineとclusterリソースの監視を開始し、常に正しいRancher Serverがclusterownerになることを保証する責任を持ちます。
whitelistproxy
whitelistproxyコントローラは、docker-machineのNode Providerを示すnodedriverリソースを監視し、Rancher APIの/meta/proxy機能のProxy先のWhitelistをメンテナンスする責任を持ちます。
/pkg/api/customization
/pkg/api/customizationは、Rancher APIの拡張のためのロジックが含まれています。
/pkg/api/customizationディレクトリの下は、リソース名ごとのディレクトリになっており、各ディレクトリは、Kubernetesからリソースを取得した後にAPIで表示するためのリソースに変換するためのロジック(formatter.go)や、APIの拡張を実装したAction Handler(actionhandler.go)が含まれます。
リスト6:pkg/api/customization
pkg/api/customization/
├── alert/
├── app/
├── authn/
├── catalog/
├── cluster/
├── actionhandler.go
├── formatter.go
└── shell.go
├── clusterregistrationtokens/
...
Rancher APIの1種であるRancherのManagement APIは、Kubernetes APIのProxyのようにふるまい、Management API経由で作成したリソースは、Kubernetes APIを通してKubernetesのリソースとして作成されます。また、逆にManagement API経由で取得するリソースは、Kubernetes APIを通してリソースが取得されます。
しかし、リソースによっては、特定のフィールドを動的に変換する必要があります。基本的な、フィールドの変換ロジックは、vendor/github.com/rancher/types/apis/management.cattle.io/v3/schema/schema.goで定義されていますが、そこでカバーできないものなどは、pkg/api/customization/<リソース名>でカバーしています。
またAction Handlerについては、リソースに対して、リソースを更新する以外のアクションを実装しています。Management APIはKubernetesのProxyとしてふるまうように設計されているため、リソースに対して作成、更新、削除以外のアクションを実施したい時に、APIが表現しづらいという課題があります。その課題に対してRancherは、各リソースの作成、削除、更新のAPI以外にActionという考え方を用意しています。
例えば、Cluster APIのActionの例として下記のAPIがあります
リスト7:Cluster APIのActionの例
https://<rancher-server>/v3/clusters/c-2t7gd?action=generateKubeconfig
これは、Clusterリソースのkubeconfigを取得するためのAPIです。「Clusterリソースのkubeconfigを取得する」という動作は、リソースの作成、削除、更新には含めづらいので、Actionとして実装されています。このように、リソースの作成、削除、更新ではないリソースに関わる動作をAPIとして提供したい際に、このpkg/api/customization/配下にactionhandlerという形で実装しています。
/pkg/api/store
続いて、紹介するのは、/pkg/api/storeになります。このディレクトリの配下も、リソース名のディレクトリになっています。各ディレクトリは、基本的にはstore.goを1つ持ち、Rancher APIよりリソースの作成、削除、更新をする際に、最終的に呼ばれるAPIのストレージレイヤーを定義しています。
基本的には、Kubernetes APIを用いてリソースを作成することになりますが、このstoreパッケージ内では、そのための前処理や後処理、またParent Kubernetesに保存するのかChild Kubernetesに保存するのかという部分をコントロールしています。
リスト8:pkg/api/store
pkg/api/store/
├── cert/
├── cluster/
├── ingress/
├── namespace/
├── node/
└── store.go
├── nodetemplate/
...
/pkg/api/server
最後に、Rancher APIに関する初期化(/pkg/api/server)に関するロジックを紹介します。このディレクトリは、次のように2つのディレクトリと1つのファイルを持ちます。
リスト9:pkg/api/server
pkg/api/server/ ├── managementstored/ ├── server.go └── userstored/
このserver.goは、Rancherのserver/server.goから呼ばれ、Rancher APIの初期化のためのエントリポイントになります。このpkg/api/server/server.goからmanagementstoredとuserstoredのロジックを評価し、CRDの作成を実施しています。また、このserver.goは、API Controllerの初期化も実施しています。





