Kubernetes ことはじめ
去年は Docker が Kubernetes のネイティブサポート を発表したり AWS にも Kubernetes をサポートする EKS がプレビュー入りしたりと Kubernetes が利用しやすくなる環境がどんどん整いつつあります。
ただ自身はこの流れに遅れて Kubernetes を触ってみたことが無かったので、ここで簡単に内容を整理してみることにしました。Kubernetes の基本的な要素である Pod, Deployment, Service を中心に見ていき、最後に既存の定義ファイルをもとに Elasticsearch クラスタを作成する、ということをやっていきます。
環境:
使用している OS が ArchLinux なので kubectl と minikube はそれぞれ AUR からインストールしました。 kubctl で表示される client と server のバージョンが違うのは普通なんですかね…?
# Version of kubectl and cluster
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.0", GitCommit:"925c127ec6b946659ad0fd596fa959be43f0cc05", GitTreeState:"clean", BuildDate:"2017-12-15T21:07:38Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"8", GitVersion:"v1.8.0", GitCommit:"0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4", GitTreeState:"clean", BuildDate:"2017-11-29T22:43:34Z", GoVersion:"go1.9.1", Compiler:"gc", Platform:"linux/amd64"}
# Version of minikube
$ minikube version
minikube version: v0.24.1
1. Kubernetes とは
- コンテナオーケストレーションツール
- コンテナ化されたアプリケーションのデプロイやスケーリングといった Linux コンテナを本番運用する上で必要となる機能を提供するもの
- Kubernetes の操作には kubectl というコマンドラインインタフェースを使用することが多い
- Kubernetes の環境を一から自分で用意するのはけっこうタフ
- クラウドベンダが (少なくとも一部分を) 管理した環境を利用するのが楽
- Goole Kubernetes Engine
- Azure Container Service
- Minikube というローカルで簡易的な環境を作れるツールもある
- クラウドベンダが (少なくとも一部分を) 管理した環境を利用するのが楽
Kubernetes をざっと理解するためには以下の内容を参考にしました。
以下では kubectl と Minikube はインストール済として進めていきます。
2. Kubernetes を構成する主要な要素
Kubernetes はコンテナオーケストレーションツールとして様々な概念を含んでいるのですが、その中でもまず理解しておきたい要素をいくつか取り上げて整理してみます。
Cluster
- 1 台以上の (物理的 or 仮想) マシンを束ねたもの
- cluster には master と呼ばれるノードとそれ以外のノードが存在する
- 前者を master, 後者を単に node と呼ぶことが多いようなので、以後そう記述する
- master は cluster の管理を行う
- node にはアプリケーションコンテナをデプロイする
もう少し詳しく見ていくと master というのは以下の 3 つのプロセスを走らせているノードであると定義されているようです (参考)。
- kube-apiserver (Kubernetes のオブジェクトを管理するための REST API を提供する)
- kube-controller-manager (cluster の状態を定期的にチェックし定義されたものに持っていく)
- kube-scheduler (どの node でコンテナを実行する等の決定を行う?)
一方 node では以下の 2 つのプロセスが実行されています。
- kubelet (master とのやり取りを行う)
- kube-proxy (ネットワーク設定のためのもの?)
(少なくとも本番環境では) master と node は別の (物理的 or 仮想) マシンに乗せるのが一般的みたいです。GKE (Google Kubernetes Engine) の最小構成も master 1, node 1 の 2 台構成になっています。
Pod
- Kubernetes ではコンテナを抽象化して Pod として扱う
- Pod には例えば以下のような情報が含まれる
- 基にした image 名
- マウントされた volume
- 環境変数
- 割り振られた private IP
- Pod とコンテナは 1 対 1 ではなく 1 対多の関係になる
- 同一 Pod に含まれるコンテナは IP, ホスト名等のリソースを共有する
上では「node でコンテナを実行する」と言ったのですが、恐らく「node で Pod を走らせる」等言った方が正確ですね。
kubectl を使用して hello world を出力する簡単な Pod を実行してみます。
# Create Pod
$ kubectl run hello-world --image busybox --restart=Never -- echo hello world
pod "hello-world" created
# Get information of pods
$ kubectl get pods -a
NAME READY STATUS RESTARTS AGE
hello-world 0/1 Completed 0 1m
# Check output of Pod
$ kubectl logs hello-world
hello world
# Delete Pod
$ kubectl delete pod hello-world
pod "hello-world" deleted
Deployment
- クラスタ内である Pod をどのように実行するか、更新するかといったことを管理する
- Deployment には以下のような情報が含まれる
- 実行したい Pod の情報
- Pod のレプリカ数
- Rollout 時の挙動 (rollout: 更新した Pod 定義の適用)
資料を見ている感じ、Pod を直接実行するより deployment を介して pod を実行するという方が多いように思います。
kubectl から kubernetes-bootcamp イメージを実行する簡単な Deployment を作成してみます。
# Create Deployment
$ kubectl run kubernetes-bootcamp --image docker.io/jocatalin/kubernetes-bootcamp:v1 --port=8080
deployment "kubernetes-bootcamp" created
# Get information of deployment
$ kubectl get deployment/kubernetes-bootcamp -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
kubernetes-bootcamp 1 1 1 1 17s kubernetes-bootcamp docker.io/jocatalin/kubernetes-bootcamp:v1 run=kubernetes-bootcamp
# Get information of pods
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
kubernetes-bootcamp-6db74b9f76-2f4xz 1/1 Running 0 2m 172.17.0.4 minikube
# Check output of Pod
$ kubectl logs $(kubectl get pods -o json | jq -r .items[0].metadata.name)
Kubernetes Bootcamp App Started At: 2018-01-01T11:27:29.059Z | Running On: kubernetes-bootcamp-6db74b9f76-4d4q
上で Pod を実行するときにも同じ kubectl run
を使用したのですが、こちらでは --restart
オプションを指定しませんでした。--restart
オプションは Pod が (正常、異常) 終了した際リスタートを行うかどうかを指定するためのもので、デフォルトは Always (常にリスタート) となります。バッチ的な処理を行うときには --restart=Never
を指定することで (Deployment を作成せず) Pod のみを作成し、一度だけコンテナの処理を行うことができます。
$ kubectl run -h
...
--restart='Always': The restart policy for this Pod. Legal values [Always, OnFailure, Never].
If set to 'Always' a deployment is created, if set to 'OnFailure' a job is created, if set to
'Never', a regular pod is created. For the latter two --replicas must be 1. Default 'Always', for
CronJobs `Never`.
...
これでアプリケーションは起動できているのですが、実はこのままだと cluster 外部からはアプリケーションにアクセスすることができません。外部からさくっとアクセス可能にするには kubectl proxy
が使えます。
$ kubectl proxy -h
Creates a proxy server or application-level gateway between localhost and the Kubernetes API Server
$ kubectl proxy
(from another terminal)
$ curl http://localhost:8001/api/v1/proxy/namespaces/default/pods/$POD_NAME/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-6db74b9f76-4d4qx | v=1
ただこれだと直接 Pod を指定しているので、複数台 (replicas >= 2) 走らせていてもアクセスを分散させることができません。この点を解消するためには Service を使用します。
Service
- Pod にアクセスするためのエンドポイントを定義する
- Service にはいくつかタイプが存在する
- ClusterIP (cluster 内にサービスを公開する)
- NodePort (cluster 外部からアクセスできるよう各 node の特定のポート上にサービスを公開する)
- LoadBalancer (cluster 外部からアクセスできるようクラウドベンダの提供するロードバランサを使用してサービスを公開する)
- ExternalName (指定した値を kube-dns の CNAME に登録する。ちょっと詳細と使いどころが?)
Deployment で定義したように同一アプリケーションが複数 Pod 走っていることがあるので、そこを意識せずにアクセスするためのオブジェクトという感じです。
他コンテナからアクセスできればいいという場合にはデフォルトの ClusterIP タイプで問題ありません。LoadBalancer は cluster 外部からアクセスしたい、かつ本番運用時にまず候補に上がるタイプだと思います。開発時やちょっと試したいというときには NodePort が使用できます。
ちなみにこれらのタイプはそれぞれ全く独立したものというわけではなく、ドキュメントによると NodePort を選択した際には自動的に ClusterIP も作成され、LoadBalancer を選択したときには NodePort, ClusterIP が自動的に作成されるという挙動になっているようです (参考)。
kubectl からは kubectl expose
で Service を作成することができます。
$ kubectl expose deployment kubernetes-bootcamp --port=80 --target-port=8080 --type=NodePort
service "kubernetes-bootcamp" exposed
$ kubectl get services -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13d <none>
kubernetes-bootcamp NodePort 10.105.32.106 <none> 80:31684/TCP 7m run=kubernetes-bootcamp
$ kubectl describe service/kubernetes-bootcamp
Name: kubernetes-bootcamp
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=kubernetes-bootcamp
Type: NodePort
IP: 10.110.121.164
Port: http 80/TCP
TargetPort: 8080/TCP
NodePort: http 32714/TCP
Endpoints: 172.17.0.4:8080,172.17.0.5:8080,172.17.0.6:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
kubectl describe
の出力を見ると Port, TargetPort, NodePort とポート関連の設定がパット見掴みづらいのですが、それぞれ「Service が受け付けるポート」、「受け付けたリクエストをコンテナに流すときに使用するポート」、「Service(type=NodePort) を作成した場合に、node 上で外部からリクエストを受け付けるために公開したポート」ということになります。
これで cluster 外部から node の ポート 31684 を介してアクセスできるようになりました。Minikube を使用しているのであれば node の IP やサービスのエンドポイントは以下のようにして確認できます (参考)。
# kubernetes-bootcamp is exposed as NodePort, so it shows the IP of the node is 192.168.99.100.
$ minikube service list
|-------------|----------------------|-----------------------------|
| NAMESPACE | NAME | URL |
|-------------|----------------------|-----------------------------|
| default | kubernetes | No node port |
| default | kubernetes-bootcamp | http://192.168.99.100:31684 |
| kube-system | kube-dns | No node port |
| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 |
|-------------|----------------------|-----------------------------|
$ minikube service kubernetes-bootcamp --url
http://192.168.99.100:31684
# Access through Service
$ curl http://192.168.99.100:31684/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-6db74b9f76-4d4qx | v=1
ここまで見た Pod, Deployment, Service の関係を図にまとめると以下のようになります。図では master 1 台、node 2 台の cluster 内に 2 replicas Pod をデプロイする構成を模しています。
3. 定義ファイルを使用したオブジェクトの作成
kubectl を使用したオブジェクトの管理方法には Kubernetes Object Management で紹介されているようにいくつか種類があります。
今までは kubectl に適当なコマンド、オプションを渡してオブジェクトを作成していました (imperative command)。これは覚えやすくちょっと試したりする分にはいいのですが、コマンドの履歴が残らないので現在のオブジェクトの状態が把握しづらく実運用でメインに使用するには少し心もとない感じでした。
ここでは imperative object configuration と呼ばれる定義ファイルを使用したオブジェクトの管理を試してみます。こちらは imperative command と違い、必要なオブジェクトの仕様を Kubernetes に伝えるという形になり、オブジェクトの管理をより厳密に行えます。
オブジェクトの API 定義は Kubernetes の公式リファレンス Reference Documentation 内で提供されており、これを見ると定義ファイルが主に以下の要素で構成されることがわかります。
apiVersion
: 対象 REST API バージョンkind
: オブジェクトの種類 (e.g. Pod, Deployment)metadata
: オブジェクトのメタ情報 (e.g. name, namespace)spec
: どういったオブジェクトが必要なのかを宣言的に指定
以下では Pod, Deployment, Service の定義ファイルを順番に見ていきます。
Pod
上で使用した hello wold をエコーする Pod を定義ファイルにしてみます。
$ cat pod-hello-world.yml
apiVersion: v1
kind: Pod
metadata:
name: hello-world
spec:
containers:
- args:
- echo
- hello world
image: busybox
name: hello-world
restartPolicy: Never
定義ファイルからは kubectl create
でオブジェクトを作成できます。
$ kubectl create -f pod-hello-world.yml
pod "hello-world" created
$ kubectl logs hello-world
hello world
オブジェクトの削除も定義ファイルを指定すれば OK です。
$ kubectl delete -f pod-hello-world.yml
pod "hello-world" deleted
Deployment
今度は kubernetes-bootcamp を 3 pods 動かす Deployment を定義してみます。
$ cat deployment-kubernetes-bootcamp.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
# Unique key of the Deployment instance
name: kubernetes-bootcamp
spec:
# 3 Pods should exist at all times.
replicas: 3
template:
metadata:
labels:
app: kubernetes-bootcamp
spec:
containers:
- name: kubernetes-bootcamp
# Run this image
image: docker.io/jocatalin/kubernetes-bootcamp:v1
ports:
- containerPort: 8080
protocol: TCP
# Create Deployment
$ kubectl create -f deployment-kubernetes-bootcamp.yml
deployment "kubernetes-bootcamp" created
# Check created Deployment
$ kubectl get deployment/kubernetes-bootcamp
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
kubernetes-bootcamp 3 3 3 3 18s
# Three kubernetes-bootcamp pods are running
$ kubectl get pods -l "app=kubernetes-bootcamp"
NAME READY STATUS RESTARTS AGE
kubernetes-bootcamp-589df98666-bbbgd 1/1 Running 0 30s
kubernetes-bootcamp-589df98666-dctdh 1/1 Running 0 30s
kubernetes-bootcamp-589df98666-qr2p9 1/1 Running 0 30s
Service
上で作成した pods に対して Service を定義してみます。
$ cat service-kubernetes-bootcamp.yml
apiVersion: v1
kind: Service
metadata:
# Unique key of the Service instance
name: kubernetes-bootcamp
spec:
ports:
# Accept traffic sent to port 80
- name: http
port: 80
targetPort: 8080
protocol: TCP
selector:
app: kubernetes-bootcamp
type: NodePort
# Create Service
$ kubectl create -f service-kubernetes-bootcamp.yml
service "kubernetes-bootcamp" created
# Check created Service
$ kubectl get service/kubernetes-bootcamp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-bootcamp NodePort 10.99.5.132 <none> 80:32057/TCP 13s
# Access through Service
$ curl http://192.168.99.100:32057
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-589df98666-c675q | v=1
このように作成したいオブジェクトの仕様がわかっていれば、あとは API リファレンスとにらめっこして定義ファイルを作成することができます。
もし既存のオブジェクトを参考にして定義ファイルを作成したい場合、 kubectl describe -o yaml
が参考になるかもしれません。
$ kubectl get -o yaml deployment/kubernetes-bootcamp
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: 2018-01-06T02:03:12Z
generation: 1
labels:
app: kubernetes-bootcamp
name: kubernetes-bootcamp
namespace: default
resourceVersion: "136990"
selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/kubernetes-bootcamp
uid: bffd72b7-f285-11e7-9eee-080027cdab72
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 2
selector:
matchLabels:
app: kubernetes-bootcamp
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: kubernetes-bootcamp
spec:
containers:
- image: docker.io/jocatalin/kubernetes-bootcamp:v1
imagePullPolicy: IfNotPresent
name: kubernetes-bootcamp
ports:
- containerPort: 8080
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 3
conditions:
- lastTransitionTime: 2018-01-06T02:03:14Z
lastUpdateTime: 2018-01-06T02:03:14Z
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: 2018-01-06T02:03:12Z
lastUpdateTime: 2018-01-06T02:03:14Z
message: ReplicaSet "kubernetes-bootcamp-589df98666" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 3
replicas: 3
updatedReplicas: 3
4. Elasticsearch クラスタのセットアップ
ここまでで一通り Kubernetes を使い始める上で大事な要素はおさえられたと思うので、最後に総まとめとして Kubernetes で Elasticsearch クラスタのセットアップを行ってみたいと思います。
とはいってもいきなり一から自分で必要な定義ファイルを作成するのはタフだったので、大部分は既存のものを参考にし、わからないところを随時確認するという形にしました。参考にしたリポジトリは kubernetes/examples と pires/kubernetes-elasticsearch-cluster です。このどちらも使用している Docker イメージは pires/docker-elasticsearch-kubernetes になります。
以下では 下図のように Minikube で作成した 1 node の Kubernetes cluster に 3 node (master, data 兼用) 構成の Elasticsearch cluster を作成していきます (Kubernetes と Elasticsearch で用語が似通っているのでちょっとややこしいですね…)。
ServiceAccount の作成
まずは ServiceAccount オブジェクトを作成します。ServiceAccount は Kubernetes 内で Pod の認証認可のために使用されるものです。Pod 作成時に明示的に定義しない場合、デフォルトのもの (default) が割り当てられるのですが、ここでは参考元にならって新規 ServiceAccount を作成しそれを使用することにします。
$ cat service-account.yml
apiVersion: v1
kind: ServiceAccount
metadata:
name: elasticsearch
$ kubectl create -f ./service-account.yml
serviceaccount "elasticsearch" created
なお ServiceAccount とはなんぞや、については以下を参考にしました。
- Configure Service Accounts for Pods
- kubernetes の認証とアクセス制御を動かしてみる
- Kubernetes 1.8 のアクセス制御について。あと Dashboard
Service の作成
次に Service を定義します。この Service では 9200, 9300 の 2 つを対象ポートにしています。これらはそれぞれ Elasticsearch の提供する外部 API にアクセスするためのもの、Elasticsearch の各ノードが相互にコミュニケーションをとるためのもの、となります。
参考元では type=LoadBalancer
でしたが、Minikube 上では利用できないのでデフォルトの ClusterIP
に変更しています。
$ cat ./es-svc.yml
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
labels:
component: elasticsearch
spec:
# type: LoadBalancer
selector:
component: elasticsearch
ports:
- name: http
port: 9200
protocol: TCP
- name: transport
port: 9300
protocol: TCP
$ kubectl create -f ./es-svc.yml
service "elasticsearch" created
$ kubectl get services -l "component=elasticsearch" -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
elasticsearch ClusterIP 10.104.228.184 <none> 9200/TCP,9300/TCP 2m component=elasticsearch
Deployment の作成
最後に少し長いですが Deployment の定義です。
$ cat es-deploy.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: es
labels:
component: elasticsearch
spec:
replicas: 3
template:
metadata:
labels:
component: elasticsearch
spec:
serviceAccount: elasticsearch
initContainers:
- name: init-sysctl
image: busybox
imagePullPolicy: IfNotPresent
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
containers:
- name: es
securityContext:
capabilities:
add:
- IPC_LOCK
image: quay.io/pires/docker-elasticsearch-kubernetes:5.6.2
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: "CLUSTER_NAME"
value: "myesdb"
- name: "DISCOVERY_SERVICE"
value: "elasticsearch"
- name: NODE_MASTER
value: "true"
- name: NODE_DATA
value: "true"
- name: HTTP_ENABLE
value: "true"
- name: NUMBER_OF_MASTERS
value: "2"
- name: ES_JAVA_OPTS
value: "-Xms256m -Xmx256m"
ports:
- containerPort: 9200
name: http
protocol: TCP
- containerPort: 9300
name: transport
protocol: TCP
volumeMounts:
- mountPath: /data
name: storage
volumes:
- name: storage
emptyDir: {}
いくつかこれまで扱っていない要素が出てきているので整理しておきます。
.spec.template.spec.serviceAccount
には Pod に割り当てる ServiceAccount を定義しています。ここでは上で作成した elasticsearch
を指定しています。
.spec.template.spec.initContainers
はメインのアプリケーションコンテナを起動する前に動かしたいコンテナを定義するところで、何らかのセットアップに使用されることが多いようです。ここでは Elasticsearch のドキュメント にも言及されている vm.max_map_count
を設定するために使用しています。
.spec.template.spec.containers[0].securityContext
では privileged
と capabilities
という 2 つの属性を設定しています。これらは恐らく Docker の privileged オプションと同じ目的の設定で、コンテナが自身のネットワークやデバイスの操作を行うためのもののようです (ref. Docker privileged オプションについて)。
.spec.template.spec.volumes
と .spec.template.spec.containers[0].volumeMounts
はコンテナにマウントする volume の設定です。前者でマウントする volume を、後者でマウント先を指定しています。ここでは volume の種類として emptyDir
というのを使用しており、これはコンテナのクラッシュではデータが消えないが、Pod を削除するとデータが消えるというある種の一時ディレクトリのような性質を持つ volume です (ref. Volumes)。
また .spec.template.spec.containers[0].env
内で NAMESPACE
のような書き方をすると Pod のメタ情報を環境変数を介してアプリケーションに伝えることができます (ref. Expose Pod Information to Containers Through Environment Variables)。
上の Deployment を kubectl create
すると、ちゃんと Elasticsearch クラスタが構築できることが確認できます。
# Create Deployment
$ kubectl create -f es-deploy.yml
deployment "es" created
# Get internal IP of Service
$ kubectl get services -l "component=elasticsearch" -o json | jq -r .items[0].spec.clusterIP
10.104.228.184
# SSH to (Kubernetes) node
$ minikube ssh
# Check (Elasticsearch) cluster status
$ curl http://10.104.228.184:9200
{
"name" : "98092ebc-6274-4a99-9588-a7e60abf29be",
"cluster_name" : "myesdb",
"cluster_uuid" : "Yq53jLFBTh-pKSAnkxxTBQ",
"version" : {
"number" : "5.6.2",
"build_hash" : "57e20f3",
"build_date" : "2017-09-23T13:16:45.703Z",
"build_snapshot" : false,
"lucene_version" : "6.6.1"
},
"tagline" : "You Know, for Search"
}
# Check number of (Elasticsearch) nodes
$ curl http://10.104.228.184:9200/_cluster/health?pretty
{
"cluster_name" : "myesdb",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 3,
"number_of_data_nodes" : 3,
"active_primary_shards" : 0,
"active_shards" : 0,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}