※ 現時点では接続先に制限があるようです。us-central1 から asia-northeast1 への接続はタイムアウトしてしまいました。us-central1の中でないと接続できない模様です。。。
Cloud Functions から GKE 上のサービスにアクセスするのに外部IPアドレスは使いたくない。
GKE 上のロードバランサで外部IP割り当てるとHTTPSの対応がマストであったりするし、何より内向けのみを前提としたサービスの場合はそもそも外部に曝したくないのであります。
そのためにひと手間加える必要がありますが、キーポイントとしては以下の2つになります。
- GKEの "Internal TCP/UDP Load Balancing"
- Functionsなどから内部IPアドレスでGCPにアクセスするための "Serverless VPC Access"、それをFunctionsで使用するための"VPC ネットワークの内部リソースに接続する"設定
の2点です。
それでは以下で、GoogleのHello Worldチュートリアルにちょっと手を加えて、FunctionsからGKE上のサービスに内部IPでアクセスする手順を示します。
1. GKE とHello Worldコンテナアプリのデプロイ
始めにKubernetesのクラスタとHello Worldを出力するだけのサンプルアプリをデプロイします。
1-1. GKE クラスタの作成
GCP の GKEコンパネからクラスタを作成します。今回は "最初のクラスタ" をテンプレートとして作成します。
クラスタでの設定のキモは、
- ネットワークの"VPCネイティブ"が
有効
であること - ネットワークが
default
であること
です。 後ほどServerless VPC Accessで、default
ネットワークに接続するように設定しますので、ここでも default
のネットワークに参加させておきます。
また、default
のネットワーク上にアクセスポイントを限定的に公開させるために、VPCネイティブ
としてdefault
ネットワーク内のIPアドレスが割り当てられるように指定します。
ノード数のスペックなどはこのままでクラスタを作成しておきましょう。
1-2. Cloud Shell の準備
これ以降の実行は GCP の管理画面から"Cloud Shell"を起動してそちらのターミナルから実施します。
以下のコマンドで対象のクラスタを指定しておきます。
gcloud container clusters get-credentials your-first-cluster-1 --zone us-central1-a --project $DEVSHELL_PROJECT_ID
クラスタ名、ゾーン、プロジェクト名などは適宜、変更してください。
1-3. Hello World アプリのコンテナ準備
今回はこちらのページのチュートリアル "Containerizing an app with Cloud Build"に従って Python でいってみます。
まずプログラムを格納するフォルダとプログラムをさくっと作成してしまいます。
$ mkdir helloworld-gke
$ cd helloworld-gke
$ cat <<EOF >> app.py
import os
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
target = os.environ.get('TARGET', 'World')
return 'Hello {}!\n'.format(target)
if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
EOF
続いて Dockerfile の準備です。
$ cat <<EOF >> dockerfile
# Use the official Python image.
# https://hub.docker.com/_/python
FROM python:3.7
# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . .
# Install production dependencies.
RUN pip install Flask gunicorn
# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 app:app
EOF
$ cat <<EOF >> .dockerignore
Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
EOF
続いて以下のコマンドで docker イメージの作成と Googleコンテナリポジトリへのアップロードを行います。
$ gcloud builds submit --tag gcr.io/$DEVSHELL_PROJECT_ID/helloworld-gke .
1-4. クラスタへアプリコンテナをデプロイ
続いて Hello Worldアプリを格納したコンテナをクラスタへデプロイしましょう。
まずは deployment 用の yaml を作成します。以下で {{プロジェクトID}}
の箇所は実際のプロジェクトIDに置き換えてください。
# This file configures the hello-world app which serves public web traffic.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: helloworld-gke
spec:
replicas: 1
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello-app
image: gcr.io/{{プロジェクトID}}/helloworld-gke:latest
# This app listens on port 8080 for web traffic by default.
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
これを以下のコマンドでクラスタに適応します。
$ kubectl apply -f deployment.yaml
1-5. 内部ロードバランサのデプロイ
肝心の要素その1である"内部ロードバランサ"をデプロイします。
以下の yaml を作成します。
apiVersion: v1
kind: Service
metadata:
name: hello
annotations:
cloud.google.com/load-balancer-type: "Internal"
spec:
type: LoadBalancer
selector:
app: hello
ports:
- port: 80
targetPort: 8080
protocol: TCP
cloud.google.com/load-balancer-type: "Internal"
がキモですね。
これをクラスタに適応します。
$ kubectl apply -f service.yaml
しばらくすると、hello ロードバランサの作成され、通常であれば外部IPアドレスが割り当てられますが、内部IPアドレスが割り当てられた service が作成されます。
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello LoadBalancer 10.0.0.xxx 10.xxx.xxx.xxx 80:31648/TCP 102m
ClusterのIPとEXTERNALのIPが10.x系に当てられているので、GCEなどのインスタンスから内部のIPでアクセスできるようになったのが分かります。
Cloud Shell 自体は10.x 系は割り当てられていないはずですので上記のIPアドレスでアクセスできませんが、利用できるGCEのインスタンスなどがあれば、curl
で確認できます。
curl http://10.xxxx.xxx.xxx/
Hello World!
2. Cloud Functionsのデプロイ
続いて、こちらのページ "VPC ネットワークに接続する" を参考に、Serverless VPC Access の設定と Cloud Functions からのアクセスを確認します。
2-1. Serverless VPC Access の準備
まず、初回のみこちらの手順を参考に Serverless VPC Accessの設定を行います。
大まかな手順としては以下の通りです。
- Serverless VPC Access API を有効にする。
- "コネクタの作成" をクリック
- 名前は適当でOKです。ここでは
internal-k8s
としておきます。 - 地域はGKEクラスタを作成した地域を指定します。(デフォルトでは
us-cental1
) - ネットワークは
default
- IP範囲はGKEが10.x系であれば10.x系のほうが良いかもしれません。ここでは
10.10.0.0
としておきます。/28
はテキストボックスの右側にうっすらとありますが勝手に補完されるので10.10.0.0/28
と入力するとエラーになります。
2-2. Cloud Functions サービスエージェントの権限設定
続いてこちらの手順を参考に、初回のみ権限の設定を行います。
大まかな手順としては以下の通りです。
- IAM のページを開く
- "Google Cloud Functions Service Agent"の名前のメンバーを探す。
- 鉛筆ボタンクリックで編集ペインを開く
- "編集者"の役割を追加
2-3. functions プログラムの作成
先ほどの GKE のロードバランサにアクセスするだけのプログラムを node.js ベースで作成してみましょう。
再び、Cloud Shell からこちらのチュートリアルを参考に、以下のようにプロジェクトを作成します。
$ mkdir ~/gcf_http
$ cd ~/gcf_http
今回は http クライアントに node-rest-client
を使用します。
$ npm init
...
$ npm i node-rest-client
続いて、以下のように index.js
を作成します。CLUSTER_IP
は上記のkubectl get services
で得られた EXTERNAL IP のIPアドレスを指定します。
var Client = require('node-rest-client').Client
var client = new Client();
const CLUSTER_IP="10.xxx.xxx.xxx";
exports.helloGET = (req, res) => {
client.get(`http://${CLUSTER_IP}`, (data, response) => {
res.send(data ? data.toString('utf8') : "ERROR");
});
};
2-4. functionsのデプロイ
それでは、先の手順で作成したinternal-k8s
コネクタを指定して functionsをデプロイしましょう。
以下のコマンドとなります。
$ gcloud beta functions deploy helloGET --trigger-http --vpc-connector projects/$DEVSHELL_PROJECT_ID/locations/us-central1/connectors/internal-k8s --runtime=nodejs8
...
httpsTrigger:
url: https://us-central1-xxxxxxx.cloudfunctions.net/helloGET
...
us-central1
やinternal-k8s
は実際に作成したコネクタに合わせて変更してください。
デプロイが完了後、ブラウザやcurl
などで上記のhttpsTrigger
で表示されたURLを叩いてみましょう。
$ curl https://us-central1-xxxxxxx.cloudfunctions.net/helloGET
Hello World!
無事に Hello World!
というメッセージが表示され、GKE上のコンテナで実行された結果が Cloud Functions から内部IP経由で取得できました。
お疲れさまでした!