先月書いた記事でDcokerでローカルにコンテナの作成をしましたが、今度はそれをKubernetesで作ってみたいと思います。
参考にした資料は下記の書籍です。
book.impress.co.jp
gihyo.jp
Kubernetesとは?
「コンテナオーケストレーションエンジン」と言われています。
現代のWebサービスなどでは、アプリケーションサーバーを複数並べてロードバランサーで分散したり、データベースをはじめとするミドルウェアごとにサーバーを立て、それぞれ分散する作りがほとんどだと思います。
Dockerがこのサーバー1台ごとのコンテナを作成するものとすると、Kuernetesはそのコンテナ化された環境を複数台並べた時の、デプロイやスケーリングなどを自動化するようなものです。
Docker for MacのKubernetes有効化
もしDocker for Macが入っていない場合は、下記からダウンロード、インストールしてください。
そしてPreferenceを開き、Kubernetesタブから「Enabel Kubernetes」を選択し、Applyを押してください。
ここから有効化するまでに少し時間がかかります。
完了すると、下記のように「Kubernetes is running」と表示されます。
Kubernetesで環境構築
Kubernetesのリソースの構成
Kubernetesはリソースと呼ばれるものの単位で管理をするのですが、今回は動かすのに最低限必要な、下記のリソースについて書きます。
- Pod
- Replicaset
- Deployment
- Service
イメージとしてはすごくざっくり書くと、
- Pod・・・コンテナが立てられているリソース
- ReplicaSet・・・Podを複数持っていて、指定された個数を保てるよう管理しているリソース
- Deployment・・・ReplicaSetを複数持ち、世代管理しているリソース
みたいな感じで、 Pod < ReplicaSet < Deployment という関係性になります。
Serviceに関しては、これらで作られた環境に対しての、ロードバランシング等を提供するリソースです。
言葉だけだと理解しづらいと思うので、実際に作っていきましょう。
Pod
Podは、Kubernetesのリソースの最小単位です。
実際にコンテナを立てるのはこのPodの中になります。
マニフェストファイルの記述
Kubernetesは、マニフェストファイルと呼ばれるYAMLファイルで、リソースの情報を記述します。
まずは前回の記事の最後にdocker-composeで作ったものと同じ環境をKubernetesのPodとして作ります。
docker-composeは下記のxmlで作成しました。
version: "3" services: hello: image: ntakehata/hello:latest ports: - 9000:8080 nginx: image: nginx:1.13.5-alpine ports: - 80:80
Kubernetesでは「マニフェストファイル」と呼ばれるYAMLファイルを記述し、リソースを作成します。
まずは次のマニフェストファイルを作ってください。
ここではsample-pod.yamlという名前にしています。
apiVersion: v1 kind: Pod metadata: name: sample-pod spec: containers: - name: hello image: ntakehata/hello:latest ports: - containerPort: 8080 - name: nginx image: nginx:1.13.5-alpine
kind
は作成するリソースの種類を指定します。今回の場合はPodになります。
metadata.name
はリソースの名前です。
spec.containers
配下はPod内に作成するコンテナの設定です。docker-composeと似たような感じですね。
name
でコンテナの名前を指定して、image
と ports
でコンテナのイメージと受け付けるポートを指定しています。
Podの作成
そして、 kubectl apply
というコマンドでPodを作成します。
ntakehata$ kubectl apply -f sample-pod.yaml pod "sample-pod" created
この kubectl apply
は、 metadata.name
で指定した名前のリソースが存在しなければ新規作成、存在すれば更新をしてくれます。
-f
は対象のマニフェストファイルのファイル名やディレクトリ名を指定するオプションです。
ここではファイル名を指定していますが、ディレクトリ名をしていすると、そのディレクトリ配下にある全てのマニフェストファイルに対して実行されます。
作成されたPodを確認します。
リソースの確認には kubectl get
というコマンドを使います。
ntakehata$ kubectl get pods NAME READY STATUS RESTARTS AGE sample-pod 2/2 Running 0 17s
今回はPodの存在を確認するので、 pods
というパラメータを付けています。
後述するReplicaSet、Deployment、Serviceといったものも、 replicasets
deployments
services
と付ければ確認できます。
また、下記のようにカンマで区切って複数のリソースをまとめて確認することもできます。
ntakehata$ kubectl get pods,replicasets,deploymentes
Pod内のコンテナに接続して確認
Podは作成しただけではlocalhostからHTTP等で接続することはできません。
これをリクエストを受けれるようにする方法は後述するので、一旦下記のコマンドで動作確認します。
ntakehata$ kubectl exec sample-pod -c hello curl http://localhost:8080 Defaulting container name to hello. Use 'kubectl describe pod/sample-pod -n default' to see all of the containers in this pod. % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 13 100 13 0 0 2096 0 --:--:-- --:--:-- --:--:-- 2600Hello World!!
kubectl exec
というコマンドを使うと、Pod上でコマンドを実行することができます。
ここではsample-pod上でcurlコマンドを実行し、hello.goを呼び出しています。
-c
オプションで指定しているのは、Pod内のアクセスしたいコンテナ名です。
もし単一のコンテナしかないPodの場合は、このオプションは不要です。
また、下記のようにすることで、Pod内のコンテナに擬似的にログインしたように操作することもできます。
ntakehata$ kubectl exec -it sample-pod -c hello /bin/bash root@sample-pod:/go#
下記のように、コマンドを実行して起動の確認もできます。
root@sample-pod:/go# curl http://localhost:8080 Hello World!!
root@sample-pod:/go# curl http://localhost <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
Podの削除
Podの削除は、 kubectl delete
というコマンドを実行します。
ntakehata$ kubectl delete pod sample-pod pod "sample-pod" deleted
パラメータとしてリソースの種類、名前を渡します。
ReplicaSet
ReplicaSetは、複数のPodの集合体のようなイメージです。
Podを指定した個数作成し、障害が発生した時は新たなPodを作成して入れ替えて復旧して、指定の個数をキープしてくれます。
ReplicaSetの作成
ReplicaSetのマニフェストファイルは、下記のようになります。
apiVersion: apps/v1 kind: ReplicaSet metadata: name: sample-replicaset spec: replicas: 3 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: hello image: ntakehata/hello:latest ports: - containerPort: 8080 - name: nginx image: nginx:1.13.5-alpine
kind
の指定を ReplicaSet
にしています。
複数のPodの集合体と書きましたが、 replicas
で指定している数値がそのPodの数(レプリカ数)になります。
また、 spec.template
配下が中に作成するPodの情報が記述されていて、その下の spec
配下はPodのマニフェストファイルと同じ内容になります。
selector
template.metadata.labesl
については後述します。
ReplicaSetの作成も、Podと同様に kubectl apply
コマンドを実行します。
ntakehata$ kubectl apply -f sample-replicaset.yaml replicaset.apps "sample-replicaset" created
そして、 kubectl get
コマンドで確認すると、下記のようになります。
ntakehata$ kubectl get pods,replicasets NAME READY STATUS RESTARTS AGE pod/sample-replicaset-5grdf 2/2 Running 0 38s pod/sample-replicaset-bkdvc 2/2 Running 0 38s pod/sample-replicaset-vb44j 2/2 Running 0 38s NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-replicaset 3 3 3 38s
sample-replicasetというReplicaSetと、それに紐づくPodが3つ作成されています。
ntakehata$ kubectl get replicasets -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR sample-replicaset 3 3 3 1m hello,nginx ntakehata/hello:latest,nginx:1.13.5-alpine app=sample-app
selector、ラベル
障害が発生した時は新たなPodを作成して入れ替えて復旧してくれると前述しましたが、落ちたかどうかの監視に「ラベル」の値が使われます。
template.metadata.labels.app
で指定している値で、ここでは sample-app
になります。
そして、なんという名前のラベルを監視対象にするかを、 selector.matchLabels.app
で指定しています。
なので template.metadata.labels.app
で指定した名前と違う名前を書いてしまうと、エラーが発生して作れないようになっています。
Deployment
Deploymentは、ReplicaSetを複数世代管理して
Deploymentの作成
Deploymentのマニフェストファイルは下記です。
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: replicas: 3 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: hello image: ntakehata/hello:latest ports: - containerPort: 8080 - name: nginx image: nginx:1.13.5-alpine
ReplicaSetとほぼ同じで、kindがDeploymentになっています。
こちらも同じく kubectl apply
で作成し、 kubectl get
で確認します。
ntakehata$ kubectl apply -f sample-deployment.yaml deployment.apps "sample-deployment" created
ntakehata$ kubectl get deployments,replicasets,pods NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.extensions/sample-deployment 3 3 3 3 15s NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-deployment-7c85f5b6b4 3 3 3 15s NAME READY STATUS RESTARTS AGE pod/sample-deployment-7c85f5b6b4-2jcg4 2/2 Running 0 15s pod/sample-deployment-7c85f5b6b4-2vt2j 2/2 Running 0 15s pod/sample-deployment-7c85f5b6b4-hrcjj 2/2 Running 0 15s
Deploymentと、それに紐づくReplicaSet、Podができあがっているのが見えます。
Deploymentの更新
作成しただけだとReplicaSetと同じに見えますが、更新するとDeploymentの意味が分かります。
ここまで前回の記事で書いた ntakehata/hello:latest
のイメージを使ってきましたが、そちらを更新します。
マニフェストファイルを下記のように変更してください。
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: replicas: 3 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: hello image: ntakehata/hello:deployment ports: - containerPort: 8080 - name: nginx image: nginx:1.13.5-alpine
コンテナ hello
のイメージを、 ntakehata/hello:deployment
に変更しました。
そして kubectl apply
で更新します。
--record
は、更新時のコマンドを履歴として残すために付けるオプションで、後ほど参照します。
ntakehata$ kubectl apply -f sample-deployment.yaml --record deployment.apps "sample-deployment" configured
すると徐々に新しいReplicaSet、Podが立ち上がり、古いPodと入れ替わっていきます。
下記は更新中に何度か kubectl get
で確認した例です。
ntakehata$ kubectl get deployments,replicasets,pods NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.extensions/sample-deployment 3 4 2 3 1d NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-deployment-74d6d58584 2 2 1 7s replicaset.extensions/sample-deployment-7c85f5b6b4 2 2 2 1d NAME READY STATUS RESTARTS AGE pod/sample-deployment-74d6d58584-85ds4 2/2 Running 0 7s pod/sample-deployment-74d6d58584-hgvxr 0/2 ContainerCreating 0 1s pod/sample-deployment-7c85f5b6b4-2jcg4 2/2 Running 0 1d pod/sample-deployment-7c85f5b6b4-2vt2j 0/2 Terminating 0 1d pod/sample-deployment-7c85f5b6b4-hrcjj 2/2 Running 0 1d
ntakehata$ kubectl get deployments,replicasets,pods NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.extensions/sample-deployment 3 4 2 3 1d NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-deployment-74d6d58584 2 2 1 10s replicaset.extensions/sample-deployment-7c85f5b6b4 2 2 2 1d NAME READY STATUS RESTARTS AGE pod/sample-deployment-74d6d58584-85ds4 2/2 Running 0 10s pod/sample-deployment-74d6d58584-hgvxr 0/2 ContainerCreating 0 4s pod/sample-deployment-7c85f5b6b4-2jcg4 2/2 Running 0 1d pod/sample-deployment-7c85f5b6b4-hrcjj 2/2 Running 0 1d
ntakehata$ kubectl get deployments,replicasets,pods NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.extensions/sample-deployment 3 4 3 3 1d NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-deployment-74d6d58584 3 3 2 14s replicaset.extensions/sample-deployment-7c85f5b6b4 1 1 1 1d NAME READY STATUS RESTARTS AGE pod/sample-deployment-74d6d58584-85ds4 2/2 Running 0 14s pod/sample-deployment-74d6d58584-hgvxr 2/2 Running 0 8s pod/sample-deployment-74d6d58584-xws5q 0/2 ContainerCreating 0 3s pod/sample-deployment-7c85f5b6b4-hrcjj 2/2 Running 0 1d
ntakehata$ kubectl get deployments,replicasets,pods NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.extensions/sample-deployment 3 3 3 3 1d NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-deployment-74d6d58584 3 3 3 28s replicaset.extensions/sample-deployment-7c85f5b6b4 0 0 0 1d NAME READY STATUS RESTARTS AGE pod/sample-deployment-74d6d58584-85ds4 2/2 Running 0 28s pod/sample-deployment-74d6d58584-hgvxr 2/2 Running 0 22s pod/sample-deployment-74d6d58584-xws5q 2/2 Running 0 17s
sample-deployment-7c85f5b6b4
のPodが徐々に減り、 sample-deployment-74d6d58584
が立ち上がっていき最終的に全て入れ替わっているのが分かります。
これをローリングアップデートと言います。
ReplicaSetは今立ち上がっているPodを更新するのに対して、Deploymentは新しいPodを立ち上げ、ヘルスチェックなどをしながら反映していってくれます。
実用ではPodやReplicaSetを直接扱うことなく、このDeploymentなどのリソースを通して構成することが基本のようです。
また、下記のコマンドで更新状況を確認することもできます。
ntakehata$ kubectl rollout status deployment sample-deployment deployment "sample-deployment" successfully rolled out
ロールバック
Deploymentは、更新した後にロールバックすることもできます。
まず、下記の kubectl rollout
コマンドで更新した履歴の確認をします。
ntakehata$ kubectl rollout history deployment sample-deployment deployments "sample-deployment" REVISION CHANGE-CAUSE 1 kubectl apply --filename=sample-deployment.yaml --record=true 2 kubectl apply --filename=sample-deployment.yaml --record=true
この中の CHANGE-CAUSE
に入っている値が、前述の更新時の --record
オプションを付けた場合に記録される内容です。
REVISION
の2が現在の状態、1が更新前の状態になります。
ロールバックは下記のコマンドで実行します。
--to-revision
で戻したいリビジョン番号を指定します。
ntakehata$ kubectl rollout undo deployment sample-deployment --to-revision 1 deployment.apps "sample-deployment"
そして、ロールバックが完了すると下記のようになります。
ntakehata$ kubectl get deployments,replicasets,pods NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.extensions/sample-deployment 3 3 3 3 1d NAME DESIRED CURRENT READY AGE replicaset.extensions/sample-deployment-74d6d58584 0 0 0 8m replicaset.extensions/sample-deployment-7c85f5b6b4 3 3 3 1d NAME READY STATUS RESTARTS AGE pod/sample-deployment-7c85f5b6b4-747j6 2/2 Running 0 19s pod/sample-deployment-7c85f5b6b4-95xht 2/2 Running 0 11s pod/sample-deployment-7c85f5b6b4-ck9fq 2/2 Running 0 15s
今度は sample-deployment-74d6d58584
のPodが消え、 sample-deployment-7c85f5b6b4
のPodが復活していますね。
Service
前述しましたが、Pod内の各コンテナに対して直接HTTP等でアクセスすることはできません。
そこでServiceというリソースを使います。
これは各Podへのロードバランシングを提供する機能です。
ClusterIPでのロードバランシング
まず、下記のマニフェストファイルでServiceを作ってみます。
apiVersion: v1 kind: Service metadata: name: sample-service spec: type: ClusterIP ports: - name: nginx port: 80 - name: go port: 8080 selector: app: sample-app
他のリソースと同様、 kind
をServiceを指定することで作成できます。
selector.app
で指定しているのが、ロードバランシングの対象とするリソースのラベルです。
先ほどDeploymentで作った「sample-app」を対象としています。
ここでは spec.type
の中で ClusterIP
を指定しています。
これはServiceに、同じクラスタの中からのみアクセスできるIPを持ったロードバランサーを作ってくれます。
なのでこのServiceではまだローカル環境から直接アクセスすることはできません。
一旦このServiceを作成します。
ntakehata$ kubectl apply -f sample-service.yaml service "sample-service" created
そして作成したServiceの情報を確認します。
ntakehata$ kubectl describe svc sample-service Name: sample-service Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-service","namespace":"default"},"spec":{"ports":[{"name":"nginx","port"... Selector: app=sample-app Type: ClusterIP IP: 10.100.151.175 Port: nginx 80/TCP TargetPort: 80/TCP Endpoints: 10.1.0.148:80,10.1.0.149:80,10.1.0.150:80 Session Affinity: None Events: <none>
kubectl describe
リソースの詳細を確認するコマンドで、「リソースの種類」「リソース名」をパラメータに渡すことで実行します。
(svcはServiceのことです)
ここで IP:
に入っている値がロードバランサーのIP、 Endpoints
に入っている値が振り分け先のPodのIPになります。
下記のコマンドでここで紐付いている想定のPodのIPを確認してみましょう。
ntakehata$ kubectl get pods -l app=sample-app -o custom-columns="NAME:{metadata.name},IP:{status.podIP}" NAME IP sample-deployment-5b447f977f-4wcbw 10.1.0.150 sample-deployment-5b447f977f-7ts2t 10.1.0.148 sample-deployment-5b447f977f-xmz4n 10.1.0.149
ServiceのEndpointsで入っていたIPと一致します。
このServiceは同じクラスタ内からのみアクセスできるので、下記のように一時的なPodを作成して、そこ経由で先ほど確認したServiceのIPに対してcurlコマンドを実行してみます。
ntakehata$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.100.151.175 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
ntakehata$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.100.151.175:8080 Hello Deployment!!
80、8080それぞれのポートでレスポンスが返ってきます。
これだけだとロードバランシングが行われているのが分かりづらいので、下記のコマンドで各Podのnginxのindex.htmlを、それぞれのhostnameが表示されるように変更します。
for PODNAME in `kubectl get pods -l app=sample-app -o jsonpath='{.items[*].metadata.name}'`; do kubectl exec -it ${PODNAME} -c nginx -- cp /etc/hostname /usr/share/nginx/html/index.html; done
そして何度かcurlを実行してみると、下記のように各Podに分散してアクセスしていることが分かります。
ntakehata$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.100.151.175 sample-deployment-5b447f977f-4wcbw ntakehata$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.100.151.175 sample-deployment-5b447f977f-xmz4n ntakehata$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.100.151.175 sample-deployment-5b447f977f-xmz4n ntakehata$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.100.151.175 sample-deployment-5b447f977f-7ts2t
NordPort ServiceでNodeの外部からアクセス可能に
ClusterIPではクラスタ内でのアクセスを可能にしましたが、最後にNordPort Serviceを使ってクラスタ外からのアクセスを可能にします。
NordPort Serviceは、Kubernetesのクラスタノードの指定されたポートに対する全てのリクエストを受け、ロードバランシングする機能を提供します。
下記のマニフェストファイルを作ってください。
apiVersion: v1 kind: Service metadata: name: sample-nodeport-service spec: type: NodePort ports: - name: nginx port: 80 targetPort: 80 nodePort: 30080 - name: go port: 8080 targetPort: 8080 nodePort: 30180 selector: app: sample-app
今度は spec.type
に NodePort
を指定しています。
spec.ports
ではいくつかのポートを記述しています。
それぞれ
- port・・・ClusterIP側で受け付けるポート
- targetPort・・・振り分け先のコンテナで受け付けるポート
- nodePort・・・Node側で受け付けるポート
になります。
Node→ClusterIP→コンテナ という流れで転送されます。
ピンと来づらいと思うので、実際に作って見てみます。
ntakehata$ kubectl apply -f sample-nodeport-service.yaml service "sample-nodeport-service" created
作成したNodeport Serviceの詳細確認。
ntakehata$ kubectl describe svc sample-nodeport-service Name: sample-nodeport-service Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-nodeport-service","namespace":"default"},"spec":{"ports":[{"name":"ngin... Selector: app=sample-app Type: NodePort IP: 10.99.244.108 LoadBalancer Ingress: localhost Port: nginx 8080/TCP TargetPort: 80/TCP NodePort: nginx 30080/TCP Endpoints: 10.1.0.148:80,10.1.0.149:80,10.1.0.150:80 Session Affinity: None External Traffic Policy: Cluster Events: <none>
ここでも IP:
に入っている値が、クラスタのIPになります。
なので例えば http://localhost:30080
にアクセスした場合
localhost:30080(nodePortで指定したポート) ↓ 10.99.244.108:80(portで指定したポート) ↓ 10.1.0.148:80 or 10.1.0.149:80 or 10.1.0.150:80(targetPortで指定したポート)
の流れで転送されることになります。
実際にローカルからcurlコマンドでアクセスしてみます。
ntakehata$ curl http://localhost:30080 sample-deployment-5b447f977f-xmz4n ntakehata$ curl http://localhost:30080 sample-deployment-5b447f977f-4wcbw ntakehata$ curl http://localhost:30080 sample-deployment-5b447f977f-7ts2t ntakehata$ curl http://localhost:30080 sample-deployment-5b447f977f-xmz4n
先ほどと同じように、各Podにロードバランシングされてnginxのコンテナにアクセスしていますね。
30180ポートにアクセスすると、goのコンテナに転送されることも確認できます。
ntakehata$ curl http://localhost:30180 Hello Deployment!!
まとめ
すごくざっくりですが、ローカルにKubernetesで環境を作り、HTTPアクセスできるところまでまとめてみました。
今回は若干間飛ばして書いてあるところはあったりとか、Podの設計とかは全く触れずとりあえずdocker-composeで作っていたものをそのままKubernetes化したりとかざっくりと動かしているので、今度はもっとちゃんとして環境を作ります。
次はPodの設計とかを考えながら、Kotlinでデータベースとかアクセスして動かせる環境を作ろうかなと思っています。