ヤフー株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。LINEヤフー Tech Blog

テクノロジー

OpenStack と Kubernetes の Service Discovery と L7 Loadbalancing を行う

はじめまして、サイトオペレーション本部の岡崎(@yutaokaz)です。
今回は、ヤフーに Gimbal という OSS を導入して OpenStack と Kubernetes の Service Discovery と L7 Loadbalancing を実現しましたので、Gimbal の紹介と実現方法をこの Tech Blog に書きたいと思います。

はじめに

サーバーは物理サーバーをそのまま利用するのではなく、仮想化を行い、その上でアプリケーションを動作させることが既に主流になっているかと思います。
ヤフーでも社内に構築した OpenStack 仮想サーバー環境や、Kubernetes コンテナ環境に移行が進んでいます。
仮想化により柔軟にサーバーの構成を変更することが可能になりましたが、冗長化の設定は手動で変更が必要であったり、複数クラスタをまたいだ冗長化が難しい、冗長化セット間に性能差があるためリソースを有効活用することが難しいなどの課題がありました。

これらの課題を解決するために、Yahoo! JAPAN, Actapio, Heptio の 3 社で共同開発を行い、Gimbal として今年の 4 月に OSS の公開を行いました。
リリース時の記事: https://blog.heptio.com/introducing-heptio-gimbal-bridging-cloud-native-and-traditional-infrastructure-9d6224bece5a

Gimbal について

Gimbal は Kubernetes 上で動作し、Service Discoverer と L7 Loadbalancer の機能を提供します。
Service Discoverer は OpenStack 用のもの、Kubernetes 用のもので別れており、指定されたクラスタの継続的な Service Discovery を行います。
L7 Loadbalancer には Envoy という OSS を利用しています。Envoy に設定を行うために Contour という OSS も同時に利用しています。
Gimbal システム全体の概要は以下の画像のようになっています。

Overview
画像引用元: https://github.com/heptio/gimbal/blob/master/docs/images/overview.png

L7 Loadbalancer の設定は IngressRoute という Kubernetes リソースを YAML ファイルに定義して、VirtualHost と Service Discoverer によって登録された Service との対応づけを行います。設定の適用には kubectl コマンドを利用し、オンラインでの設定変更が可能になっています。

次項では実際に Gimbal を利用したサービス提供を行いながら、もう少し詳細を見ていきたいと思います。

Gimbal を利用したサービス提供

Gimbal による Service Discovery と L7 Loadbalancing 機能を利用したサービス提供を試してみたいと思います。

サービスの準備

OpenStack と Kubernetes 上にそれぞれサービスを作成します。

OpenStack サービス作成

下記の通り、80, 8000 ポートでサービスを提供するようなウェブサーバー 2 台を冗長化した loadbalancer を OpenStack project: yutaokaz-advent-calendar に作成しました。

$ neutron lbaas-loadbalancer-show yutaokaz-service
+---------------------+------------------------------------------------+
| Field               | Value                                          |
+---------------------+------------------------------------------------+
| admin_state_up      | True                                           |
| description         |                                                |
| id                  | 8baa7a9b-d723-4558-be23-871b778a9f7d           |
| listeners           | {"id": "953df759-1f21-4bf0-9c34-c150f6b9a9b0"} |
|                     | {"id": "5d872299-158f-41fd-b108-0c980d59041d"} |
| name                | yutaokaz-service                               |
| operating_status    | ONLINE                                         |
| pools               | {"id": "3dddbff9-5be4-4df8-8e74-3cfe5d110998"} |
|                     | {"id": "5cb11f6d-7744-4336-8369-16a6a24032a9"} |
| provider            | haproxy                                        |
| provisioning_status | ACTIVE                                         |
| tenant_id           | 2facfc7214a64c2898f2584fe8b5fff3               |
| vip_address         | < VIP >                                        |
| vip_port_id         | 851c5844-7e2e-4f2b-8123-9a1e0d85904a           |
| vip_subnet_id       | 81fa2f8e-f514-4bbd-8190-ccbecb87a558           |
+---------------------+------------------------------------------------+

HTTP レスポンスはどのサーバーからのレスポンスなのかを識別できるようにしてみました。

$ curl $VIP
OpenStack) server01:80
$ curl $VIP
OpenStack) server02:80
$ curl $VIP:8000
OpenStack) server01:8000
$ curl $VIP:8000
OpenStack) server02:8000

Kubernetes サービス作成

Kubernetes 上にも同様にサービスを作成していきます。
Namespace は先程作成した OpenStack project 名と同じ yutaokaz-advent-calendar にしておきます。下記に今回作成したサービスの YAML ファイルを記載します。 (長くなりますので 02-service8000.yaml は省略します。)

01-namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: yutaokaz-advent-calendar

02-service80.yaml

apiVersion: v1
kind: Service
metadata:
  namespace: yutaokaz-advent-calendar
  name: yutaokaz-service80
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: yutaokaz-service80
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  namespace: yutaokaz-advent-calendar
  name: yutaokaz-service80
  labels:
    app: yutaokaz-service80
spec:
  replicas: 1
  selector:
    matchLabels:
      app: yutaokaz-service80
  template:
    metadata:
      labels:
        app: yutaokaz-service80
    spec:
      containers:
      - image: httpd:2.4
        name: yutaokaz-service80
        command:
        - "/bin/bash"
        - "-c"
        - |
          echo 'Kubernetes) service80' > htdocs/index.html && httpd-foreground
      hostNetwork: true

ロードバランシングのために Gimbal をデプロイする Kubernetes クラスタと Kubernetes サービス Pod は IP 疎通が必要です。hostNetwork: true を指定することで Pod は ClusterIP でなく、NodeIP を利用しますので、Gimbal の稼働するクラスタから疎通が可能になります。
早速これらのファイルを利用して、Kubernetes へサービスをデプロイしてみます。

$ kubectl apply -f advent-calendar/
namespace/yutaokaz-advent-calendar created
service/yutaokaz-service80 created
deployment.extensions/yutaokaz-service80 created
service/yutaokaz-service8000 created
deployment.extensions/yutaokaz-service8000 created

$ kubectl get namespaces yutaokaz-advent-calendar
NAME                       STATUS   AGE
yutaokaz-advent-calendar   Active   35s

$ kubectl get deployments -n yutaokaz-advent-calendar -o wide
NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS             IMAGES      SELECTOR
yutaokaz-service80     1         1         1            1           102s   yutaokaz-service80     httpd:2.4   app=yutaokaz-service80
yutaokaz-service8000   1         1         1            1           101s   yutaokaz-service8000   httpd:2.4   app=yutaokaz-service8000

$ kubectl get services -n yutaokaz-advent-calendar -o wide
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE    SELECTOR
yutaokaz-service80     ClusterIP   10.99.224.250   <none>        80/TCP     2m8s   app=yutaokaz-service80
yutaokaz-service8000   ClusterIP   10.108.51.131   <none>        8000/TCP   2m7s   app=yutaokaz-service8000

$ kubectl get pods -n yutaokaz-advent-calendar -o wide
NAME                                    READY   STATUS    RESTARTS   AGE     IP              NODE             NOMINATED NODE
yutaokaz-service80-bdd85b9df-zthlv      1/1     Running   0          2m12s   < Node1IP >     yutaokaz-node1   <none>
yutaokaz-service8000-7bcdf747d6-4n7gs   1/1     Running   0          2m12s   < Node2IP >     yutaokaz-node2   <none>

問題なくデプロイできているようです。念のため HTTP レスポンスも確認しておきます。

$ curl $Node1IP
Kubernetes) service80
$ curl $Node2IP:8000
Kubernetes) service8000

レスポンスも期待した通りに得られているようです。
これでサービスの準備が完了しました。次は先程作成した OpenStack サービスと一緒に Service Discovery を行っていきます。

Service Discoverer の起動

先程作成した Kubernetes サービスクラスタとは別の Kubernetes クラスタ上に Service Discoverer をデプロイして、OpenStack と Kubernetes それぞれの Service Discovery を行ってみます。
ここからの手順は Gimbal README にも記載がありますので参考にしてみてください。

Service Discoverer 準備

今回は Gimbal Repository のファイルを利用して進めていきたいと思いますので、Git からソースコードを取得しておきます。

$ git clone https://github.com/heptio/gimbal.git
$ cd gimbal/deployment/

Service Discoverer を起動する前に、デプロイ先となる Kubernetes Namespace を作成しておきます。

$ kubectl apply -f gimbal-discoverer/01-common.yaml
namespace/gimbal-discovery created
serviceaccount/gimbal-discoverer created
clusterrolebinding.rbac.authorization.k8s.io/gimbal-discoverer created
clusterrole.rbac.authorization.k8s.io/gimbal-discoverer created

namespace/gimbal-discovery が作成されました。
Service Discoverer はサービスを作成した OpenStack project 名または Kubernetes Namespace 名でサービスの登録を行いますので、この Kubernetes クラスタにも yutaokaz-advent-calendar Namespace を作成しておきます。

$ kubectl create namespace yutaokaz-advent-calendar
namespace/yutaokaz-advent-calendar created

ここまでで準備は完了です。次は Service Discoverer を起動して、サービスを登録していきます。

OpenStack Service Discoverer

OpenStack Service Discoverer は OpenStack Identity v3 APILBaaS v2 API を利用して、Service Discovery を行います。

OpenStack admin ユーザーとパスワード、OpenStack Keystone URL, https 通信時の CA 証明書を Kubernetes Secret として登録しておきます。
backend-name は複数の仮想基盤クラスタの Service Discovery を行ってもサービスの区別がつくように、Kubernetes Service 名の接頭語として利用されます。

$ kubectl -n gimbal-discovery create secret generic remote-discover-openstack \
    --from-literal=username=admin \
    --from-literal=password=abc123 \
    --from-literal=auth-url=https://api.openstack:5000/ \
    --from-file=certificate-authority-data=./ca.pem \
    --from-literal=backend-name=os \
    --from-literal=tenant-name=yutaokaz-advent-calendar
secret/remote-discover-openstack created

OpenStack Service Discoverer をデプロイします。

$ kubectl apply -f gimbal-discoverer/02-openstack-discoverer.yaml
deployment.extensions/openstack-discoverer created

# デプロイの確認
$ kubectl get pods -n gimbal-discovery
NAME                                   READY   STATUS    RESTARTS   AGE
openstack-discoverer-f97f6c495-gkr77   1/1     Running   0          15s
# ログの確認
$ kubectl logs -n gimbal-discovery openstack-discoverer-f97f6c495-gkr77

特にエラー等出力されていなければデプロイは成功です。
OpenStack の Service Discovery が始まっていますので、確認してみます。

$ kubectl get services -n yutaokaz-advent-calendar
NAME                                      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)           AGE
os-8baa7a9b-d723-4558-be23-871b778a9f7d   ClusterIP   None         <none>        8000/TCP,80/TCP   50s
$ kubectl get endpoints -n yutaokaz-advent-calendar
NAME                                      ENDPOINTS                                                            AGE
os-8baa7a9b-d723-4558-be23-871b778a9f7d   < Server1IP >:8000,< Server2IP >:8000,< Server1IP >:80 + 1 more...   70s

OpenStack loadbalancer が Kubernetes Service として、OpenStack pool member が Kubernetes Endpoint として、それぞれ登録されました。登録名は指定した接頭語と loadbalancer UUID を繋げたものになっています。
OpenStack Discoverer はデフォルトで 30 秒ごとに OpenStack から情報を取得し、変更があれば Kubernetes の情報を変更しますので、OpenStack loadbalancer の追加・削除や構成変更を行っても、Gimbal クラスタ側での作業は発生しません。

Kubernetes Service Discoverer

続けて、Kubernetes の Service Discovery を行ってみます。
OpenStack Discoverer の時と同様に Kubernetes Discoverer にも Kubernetes Secret を用意します。

$ kubectl -n gimbal-discovery create secret generic remote-discover-kubecfg \
    --from-file=config=./config \
    --from-literal=backend-name=k8s
secret/remote-discover-kubecfg created

config には Service Discovery 対象の Kubernetes Config ファイルパスを指定します。backend-name は OpenStack Discoverer 同様、サービス名の接頭語として利用されます。
準備ができましたので、Kubernetes Service Discoverer をデプロイします。

$ kubectl apply -f gimbal-discoverer/02-kubernetes-discoverer.yaml
deployment.extensions/k8s-kubernetes-discoverer created

# デプロイの確認
$ kubectl get pods -n gimbal-discovery
NAME                                        READY   STATUS    RESTARTS   AGE
k8s-kubernetes-discoverer-78975d5cf-gbw6f   1/1     Running   0          3s
openstack-discoverer-54588f4bbf-vxxxn       1/1     Running   0          17m
# ログの確認
$ kubectl logs -n gimbal-discovery k8s-kubernetes-discoverer-78975d5cf-gbw6f

正常に起動しているようであれば、Service Discovery の結果を確認してみます。

$ kubectl get services -n yutaokaz-advent-calendar
NAME                                      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)           AGE
k8s-yutaokaz-service80                    ClusterIP   None         <none>        80/TCP            30s
k8s-yutaokaz-service8000                  ClusterIP   None         <none>        8000/TCP          30s
os-8baa7a9b-d723-4558-be23-871b778a9f7d   ClusterIP   None         <none>        8000/TCP,80/TCP   17m
$ kubectl get endpoints -n yutaokaz-advent-calendar
NAME                                      ENDPOINTS                                                            AGE
k8s-yutaokaz-service80                    < Node1IP >:80                                                       36s
k8s-yutaokaz-service8000                  < Node2IP >:8000                                                     36s
os-8baa7a9b-d723-4558-be23-871b778a9f7d   < Server1IP >:8000,< Server2IP >:8000,< Server1IP >:80 + 1 more...   17m

k8s- から始まる Kubernetes Service と Endpoint が追加されました。

今回は OpenStack クラスタ、Kubernetes クラスタともに 1 クラスタずつの Service Discovery を行ってみましたが、Kubernetes Secret と Service Discoverer のリソース名を変えて複数起動することで、複数クラスタの Service Discovery を行うことが可能になっています。

L7 Loadbalancer の作成

Service Discovery によって対象クラスタでの変更情報が継続的に反映されるようになりました。ここからは登録されたサービス情報を利用した L7 Loadbalancing を行い、外部にサービス公開をしていきたいと思います。

Contour, Envoy のデプロイ

L7 Loadbalancing を行う Envoy と、設定情報を提供するための Contour をデプロイします。

$ kubectl apply -f contour/
namespace/gimbal-contour created
serviceaccount/contour created
customresourcedefinition.apiextensions.k8s.io/ingressroutes.contour.heptio.com created
clusterrolebinding.rbac.authorization.k8s.io/contour created
clusterrole.rbac.authorization.k8s.io/contour created
service/contour created
service/envoy created
configmap/envoy-statsd created
deployment.apps/contour created
daemonset.extensions/envoy created

起動を確認してみると、各 Kubernetes Node 上に Contour: 1, Envoy: 2 pod が起動していることが分かります。

$ kubectl get pods -n gimbal-contour
NAME                       READY   STATUS    RESTARTS   AGE
contour-76b7cd5d88-8f2xw   1/1     Running   0          17s
contour-76b7cd5d88-f6lxh   1/1     Running   0          17s
envoy-jjcm2                2/2     Running   0          17s
envoy-x74tk                2/2     Running   0          17s

これでロードバランスの準備は整いました。

L7 Loadbalancer 設定

起動したロードバランサの設定を行ってみます。下記のような YAML ファイルを作成します。

---
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
  name: yutaokaz-advent-calendar
  namespace: yutaokaz-advent-calendar
spec:
  virtualhost:
    fqdn: yutaokaz-advent-calendar.local
  routes:
    - match: /
      services:
        - name: os-8baa7a9b-d723-4558-be23-871b778a9f7d
          port: 80
        - name: k8s-yutaokaz-service80
          port: 80

fqdn: yutaokaz-advent-calendar.local のエンドポイント / に OpenStack, Kubernetes のサービスを割り当てています。早速デプロイしてみます。

$ kubectl apply -f ingressroute.yaml
ingressroute.contour.heptio.com/yutaokaz-advent-calendar created

$ kubectl get ingressroutes -n yutaokaz-advent-calendar
NAME                       FQDN                             TLS SECRET   FIRST ROUTE   STATUS   STATUS DESCRIPTION
yutaokaz-advent-calendar   yutaokaz-advent-calendar.local                /             valid    valid IngressRoute

無事にデプロイできました。YAML 定義にエラーがあった場合にはエラー終了しますので、メッセージに従ってファイルを修正してください。
kubectl get では設定した FQDN や ROUTE, STATUS の情報が出力されていることが確認できます。Gimbal Kubernetes クラスタ上で稼働している Envoy にリクエストして、レスポンスを確認してみます。

$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
OpenStack) server01:80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
Kubernetes) service80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
OpenStack) server02:80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
Kubernetes) service80

設定した通り、OpenStack と Kubernetes にロードバランスされています。
(デフォルトでは RoundRobin にてロードバランスされますが、正確にラウンドロビンで割り振られる訳ではないようです。レスポンスは分かりやすく表示しています。)

L7 Loadbalancing の機能も試してみたいと思います。
/openstack にアクセスしたときは、OpenStack の 8000 ポートにアクセスするようにしてみます。
サービスは複数ポート混ぜることも可能ですので、/ へのアクセスは複数ポートを混ぜた定義にしてみます。さらに OpenStack サービスは 1 Service に 2 台が設定されているので、weight を設定して各 Endpoint に均等にリクエストが到着するようにしてみます。
変更後の YAML ファイルを下記に記載します。

---
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
  name: yutaokaz-advent-calendar
  namespace: yutaokaz-advent-calendar
spec:
  virtualhost:
    fqdn: yutaokaz-advent-calendar.local
  routes:
    - match: /
      services:
        - name: k8s-yutaokaz-service80
          port: 80
          weight: 25
        - name: k8s-yutaokaz-service8000
          port: 8000
          weight: 25
        - name: os-8baa7a9b-d723-4558-be23-871b778a9f7d
          port: 80
          weight: 50
    - match: /openstack
      services:
        - name: os-8baa7a9b-d723-4558-be23-871b778a9f7d
          port: 8000

設定の適用とレスポンスの確認をしてみます。設定変更はサービス断なしに行うことができます。

# apply
$ kubectl apply -f ingressroute.yaml
ingressroute.contour.heptio.com/yutaokaz-advent-calendar configured

# weight, mixed port
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
OpenStack) server01:80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
OpenStack) server02:80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
Kubernetes) service80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
Kubernetes) service8000

# /openstack
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP/openstack/
OpenStack) /openstack server01:8000
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP/openstack/
OpenStack) /openstack server02:8000

オンラインでの設定変更と L7 Loadbalancing, weight 設定機能を試してみました。
実サービスで利用する際には各 Endpoint へヘルスチェックを行い、障害時やメンテナンス時はサービスアウトさせる、ヘルスチェック機能があると便利です。Envoy にはヘルスチェックの機能がありますので利用してみます。
OpenStack VM 2 台の内の 1 台に /healthz へのレスポンスを設定してみました。

$ curl -si $Server1IP/healthz | head -1
HTTP/1.1 200 OK
$ curl -si $Server2IP/healthz | head -1
HTTP/1.1 404 Not Found

サービスにヘルスチェックを設定するには以下のように記述します。

---
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
  name: yutaokaz-advent-calendar
  namespace: yutaokaz-advent-calendar
spec:
  virtualhost:
    fqdn: yutaokaz-advent-calendar.local
  routes:
    - match: /
      services:
        - name: os-8baa7a9b-d723-4558-be23-871b778a9f7d
          port: 80
          healthCheck:
            path: /healthz
            intervalSeconds: 10

設定を反映して、レスポンスを確認してみます。

# apply
$ kubectl apply -f ingressroute.yaml
ingressroute.contour.heptio.com/yutaokaz-advent-calendar configured

# request
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
OpenStack) server01:80
$ curl -H 'Host: yutaokaz-advent-calendar.local' $GimbalNodeIP
OpenStack) server01:80

server02 はサービスアウトと判断され、リクエストが割り振られなくなりました。
さらに細かいヘルスチェックの設定や、バランシング方法など、今回紹介できなかった機能もありますので、ぜひドキュメントも確認してみてください。

Gimbal とサービスの監視

Gimbal にはサービス監視のために GrafanaPrometheus も用意されています。
Gimbal の稼働している Kubernetes クラスタにデプロイすることで、各 Service Discoverer と Envoy のステータスを Prometheus がメトリクスを取得して、Grafana で確認できるようになっています。
デフォルトのダッシュボードでは Service Discoverer の状況、各サービスへの RPS や Latency などの情報を表示しています。

Gimbal Discovery
Gimbal Discovery

Contour
Contour

Envoy
Envoy

サービスがサーバーエラーのレスポンスステータスコードを送信していたり、Latency の閾値を超えたなどの異常を検知した際には Alert Manager を利用したアラート通知を行うことも可能です。

Gimbal の制限

Gimbal を利用するには下記のような制限がありますので、利用する際には注意が必要です。
ヤフーでの対応はインデントを下げて記載しています。

  • プロトコルは http/https のみ対応しています。
  • ロードバランスを行うために、Gimbal クラスタからサービス Pod への IP 疎通が必要です。
  • ユーザーからのリクエストは Gimbal の稼働している Kubernetes Node へ到着するので、Node の冗長化は別途検討が必要です。
    • Kubernetes Node の冗長化にはハードウエアロードバランサを利用しています。
  • IngressRoute VirtualHost Name 設定が重複するとエラーになります。
    • IngressRoute VirtualHost を作成できる Kubernetes Namespace を admin に制限し、ユーザーには VirtualHost の作成を許可しない運用を行っています。
    • Delegation の機能を利用することで、ユーザーは VirtualHost 内で自由に path やヘルスチェックの設定を行うことが可能です。

最後に

ここまで Gimbal を利用した OpenStack と Kubernetes の Service Discovery と L7 Loadbalancing の実現方法を紹介してきましたが、いかがでしたでしょうか。
Service Discovery 対象数にもよりますが、Discovery も十分に高速ですのでサービス提供までのスピードをひとつ上げることができるのではないかと思っています。

複数の仮想基盤クラスタをまとめたロードバランスが可能で、設定変更がサービス断なしに行えますので、新機能のカナリアデプロイを行う、サービスの高負荷を見越してクラスタリソースを追加しておく、高負荷が過ぎたらクラスタごと捨てる、仮想化基盤クラスタの移行を段階的に行う、仮想化基盤クラスタ自体のメンテナンスを行う等々、今まで難しかったさまざまなことが実現可能になるのではないかと期待しています。
先に述べたような複雑なことをしないまでも、Gimbal には Service Discovery の機能があり、L7 Loadbalancer である Envoy の性能が良く、ロードバランス先へのヘルスチェック機能もありますので、現在利用しているハードウエア/ソフトウエアロードバランサの置き換えも検討できるかもしれません。

ヤフーではまだ開発環境への導入に留まっていますが、今後本番環境にも展開していきたいと思っています。Gimbal の運用や開発、活用方法などで有益な情報が得られましたら、また何かしらの形で共有したいと思います。
もし検証や導入をされた方がいましたら、ブログなどで共有していただけるととてもうれしいです。
それでは明日のアドベントカレンダーもお楽しみに。

こちらの記事のご感想を聞かせください。

  • 学びがある
  • わかりやすい
  • 新しい視点

ご感想ありがとうございました

このページの先頭へ