CLOVER🍀

That was when it all began.

OKD/Minishift上で、None-Selector ServiceとExternalName Serviceを試す

これは、なにをしたくて書いたもの?

なんとなく、それ用のServiceがあることはぼんやりと知っていたので、今回こちらをOKD/Minishiftで試してみました。

OKD(Kubernetes)の外部リソースへのアクセス方法を提供するService

ドキュメントでいくと、このあたりです。

Integrating External Services | Developer Guide | OKD 3.11

Defining a service / Services without selectors

外部リソースに対するServiceを定義することで、以下のようなことができるようになります。

  • クラスタ外にあるサーバー、例えばデータベースなどに、KubernetesのServiceの名前でアクセスが可能になる
  • 外部サーバーのエンドポイントを、アプリケーションなどに直接記述しなくてよくなる

とまあ、外部のリソースであっても、Kubernetes内でアクセスかのようにコントロールできるようになるわけですね。

外部リソースに対する、ロードバランサーを立てているようなものでしょうけれど、クラスタ内のService名で解決できるところが
ポイントですね、と。

ここで利用するServiceは、接続先への定義によって次の2種類を使います。

  • 接続先をIPアドレスで指定する場合 … None-Selector Service(Service+Endpoints)
  • 接続先を名前で定義する場合 … ExternalName Service

今回は、両方扱います。

お題

OKD(Minishift)が稼働するサーバーとは別のサーバーに、ApacheMySQLを立てて、こちらに対してService越しにアクセス
させてみようと思います。

ApacheおよびMySQLが動作するサーバーの情報は、以下の通り。

名前は、OKD(Minishift)が動作している側のホストの「/etc/hosts」に直接記載しました。

192.168.0.3  test.server

また、外部リソースへアクセスする際のService名は以下のようにします。

Serviceの定義方法に関わらず、この名前でいきます。つまり、パターンを切り替える場合は、いったんリソースを削除します。

環境

今回の環境は、こちら。

$ minishift version
minishift v1.30.0+186b034


$ oc version
oc v3.11.0+0cbc58b
kubernetes v1.11.0+d4cacc0
features: Basic-Auth GSSAPI Kerberos SPNEGO

Server https://192.168.42.198:8443
kubernetes v1.11.0+d4cacc0

Apacheは2.4.29、MySQLは8.0.14を使用します。

Apacheは、Ubuntuにaptでインストールできるデフォルトのものです。

f:id:Kazuhira:20190127185045p:plain

サンプルアプリケーション

では、先にServiceを使ってアクセスするアプリケーションを書いてみます。Node.jsで書くことにしましょう。

Expressでサーバーを書くことにして、ApacheおよびMySQLにリクエストを投げるアプリケーションにしましょう。

$ npm install express promise-mysql request-promise request

インストールされたパッケージのバージョン。

  "dependencies": {
    "express": "^4.16.4",
    "promise-mysql": "^3.3.1",
    "request": "^2.88.0",
    "request-promise": "^4.2.2"
  }

「/apache2」でApacheに向けてリクエストを投げて、その結果をそのままレスポンスとして返す、また「/mysql」でMySQL
接続し、現在時刻を返す簡単なアプリケーションを作成します。 server.js

const express = require("express");
const rp = require("request-promise");
const mysql = require("promise-mysql");

const app = express();

app.get("/apache2", async (req, res) => {
    try {
        rp("http://external-apache2-service")
            .then(html => res.send(html));
    } catch (e) {
        console.log(e);
    }
});

app.get("/mysql", async (req, res) => {
    try {
        const connection = await mysql.createConnection({
            host: "external-mysql-service",
            port: 3306,
            user: "kazuhira",
            password: "password",
            database: "practice"
        });

        const rows = await connection.query('select now() as message');

        res.send(`${rows[0]['message']}`);
    } catch (e) {
        console.log(e);
    }
});

app.listen(8080);

ところで、今回はNode.jsを使っているのに対してMySQLが8.0なので、認証時にエラーにならないようにユーザー作成時に
mysql_native_password」をつけておきました。
MySQLの設定ファイルに記載する方法でもよいと思います

CREATE USER ... IDENTIFIED WITH mysql_native_password BY ...;

あとは、こちらをOKDにデプロイします。Routeも作成。

$ oc new-app --name nodejs-external-service [Gitリポジトリへのパス]
$ oc expose svc/nodejs-external-service

以上で、アプリケーションの準備は完了です。

外部リソースへのアクセスをIPアドレスで定義する(None-Selector Service/Service+Endpoints)

まずは、外部リソースへのアクセスをIPアドレスで行う方法で設定してみましょう。

作成したYAMLは、こんな感じ。Apacheへ直接アクセスするRouteもありますが、オマケです。 external-resource-endpoints.yml

---
### Service(Apache)
apiVersion: v1
kind: Service
metadata:
  labels:
    app: external-apache2
  name: external-apache2-service
spec:
  ports:
    - name: external-apache2-http
      protocol: TCP
      port: 80
      targetPort: 80

---
### Service(MySQL)
apiVersion: v1
kind: Service
metadata:
  labels:
    app: external-mysql
  name: external-mysql-service
spec:
  ports:
    - name: external-mysql
      protocol: TCP
      port: 3306
      targetPort: 3306


---
### Endpoints(Apache)
apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: external-apache2
  name: external-apache2-service
subsets:
- addresses:
  - ip: 192.168.0.3
  ports:
  - name: external-apache2-http
    port: 80

---
### Endpoints(MySQL)
apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: external-mysql
  name: external-mysql-service
subsets:
- addresses:
  - ip: 192.168.0.3
  ports:
  - name: external-mysql
    port: 3306


---
### Route(http)
apiVersion: v1
kind: Route
metadata:
  labels:
    app: external-apache2
  name: external-apache2-http-endpoint
spec:
  port:
    targetPort: external-apache2-http
  to:
    kind: Service
    name: external-apache2-service

概ねこちらのドキュメントを読めばいいのですが、いくつかポイントがあります。

Integrating External Services | Developer Guide | OKD 3.11

ApacheのServiceとEndpointsを例にして、書いていきましょう。

まずは、Serviceを定義します。この時、selectorを定義しないようにします。

### Service(Apache)
apiVersion: v1
kind: Service
metadata:
  labels:
    app: external-apache2
  name: external-apache2-service
spec:
  ports:
    - name: external-apache2-http
      protocol: TCP
      port: 80
      targetPort: 80

続いて、Endpointsを定義します。この時、「metadata.name」と「spec.ports.name」は、先に定義したServiceでの定義と
名前を一致させる必要があります。

apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: external-apache2
  name: external-apache2-service
subsets:
- addresses:
  - ip: 192.168.0.3
  ports:
  - name: external-apache2-http
    port: 80

では、作成してみます。

$ oc apply -f external-resource-endpoints.yml

Serviceの確認。typeが「ClusterIP」、Selectorは設定されていません。

$ oc get svc -o wide
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE       SELECTOR
external-apache2-service   ClusterIP   172.30.246.243   <none>        80/TCP     22s       <none>
external-mysql-service     ClusterIP   172.30.179.61    <none>        3306/TCP   22s       <none>
nodejs-external-service    ClusterIP   172.30.145.202   <none>        8080/TCP   10m       app=nodejs-external-service,deploymentconfig=nodejs-external-service

Endpoints。

$ oc get ep -o wide
NAME                       ENDPOINTS          AGE
external-apache2-service   192.168.0.3:80     25s
external-mysql-service     192.168.0.3:3306   25s
nodejs-external-service    172.17.0.6:8080    10m

確認してみましょう。

## Apache
$ curl -I nodejs-external-service-myproject.192.168.42.198.nip.io/apache2
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 10918
ETag: W/"2aa6-B5k4N85/AnOmWyDbjumySCPafh4"
Date: Sun, 27 Jan 2019 10:10:05 GMT
Set-Cookie: f2a3c659b2659592c251c5e8fdf65988=71bc8243604809abfd59fec26feb237c; path=/; HttpOnly
Cache-control: private


## MySQL
$ curl nodejs-external-service-myproject.192.168.42.198.nip.io/mysql
Sun Jan 27 2019 10:10:34 GMT+0000 (Coordinated Universal Time)

ちょっとわかりにくいですが、Node.jsのアプリケーション越しに、外部リソース上のApacheおよびMySQLにアクセスできました。

オマケ的に定義したRouteを使っても、アクセス可能です。

$ curl -I external-apache2-http-endpoint-myproject.192.168.42.198.nip.io
HTTP/1.1 200 OK
Date: Sun, 27 Jan 2019 10:11:27 GMT
Server: Apache/2.4.29 (Ubuntu)
Last-Modified: Sun, 27 Jan 2019 07:14:02 GMT
ETag: "2aa6-5806b4dff8280"
Accept-Ranges: bytes
Content-Length: 10918
Vary: Accept-Encoding
Content-Type: text/html
Set-Cookie: c10e6db40be63af5d822808c349366a4=41d3256b7efc42ef62303ae9e497f809; path=/; HttpOnly
Cache-control: private

では、ここでリソースを1度全部削除しておきます。

$ oc delete all --all

Endpointsは、同じ名前で定義したServiceを削除すると、一緒に削除されます。

また、次のためにアプリケーションを再度デプロイしておきます。

$ oc new-app --name nodejs-external-service [Gitリポジトリへのパス]
$ oc expose svc/nodejs-external-service

外部リソースへのアクセスを名前で定義する(ExternalName Service)

続いて、ExternalName Serviceを使う場合。

作成したYAMLはこんな感じで、やっぱりApacheにアクセスするためのRouteも用意しました。
external-resource-externalname.yml

---
### Service(Apache)
apiVersion: v1
kind: Service
metadata:
  labels:
    app: external-apache2
  name: external-apache2-service
spec:
  type: ExternalName
  externalName: test.server
  ports:
    - name: external-apache2-http
      protocol: TCP
      port: 80
      targetPort: 80

---
### Service(MySQL)
apiVersion: v1
kind: Service
metadata:
  labels:
    app: external-mysql
  name: external-mysql-service
spec:
  type: ExternalName
  externalName: test.server
  ports:
    - name: external-mysql
      protocol: TCP
      port: 3306
      targetPort: 3306


---
### Route(http)
apiVersion: v1
kind: Route
metadata:
  labels:
    app: external-apache2
  name: external-apache2-http-endpoint
spec:
  port:
    targetPort: external-apache2-http
  to:
    kind: Service
    name: external-apache2-service

こちらは単純で、typeが「ExternalName」なServiceを定義し、「externalName」でアクセス先のサーバー名を設定してあげれば
OKです。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: external-apache2
  name: external-apache2-service
spec:
  type: ExternalName
  externalName: test.server
  ports:
    - name: external-apache2-http
      protocol: TCP
      port: 80
      targetPort: 80

ドキュメントがそうなっていますが、「spec.ports」は書かなくてもOKです。その場合、ポートを絞らずにリクエストを
転送する感じになります。

では、リソースを定義します。

$ oc apply -f external-resource-externalname.yml

作成されたServiceを見てみます。typeは「ExternalName」、またclusterIPは設定されていませんね。代わりにexternalIPの
記述があります。

$ oc get svc -o wide
NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE       SELECTOR
external-apache2-service   ExternalName   <none>         test.server   80/TCP     21s       <none>
external-mysql-service     ExternalName   <none>         test.server   3306/TCP   21s       <none>
nodejs-external-service    ClusterIP      172.30.10.66   <none>        8080/TCP   6m        app=nodejs-external-service,deploymentconfig=nodejs-external-service

同じくNode.jsからアクセスできますが、結果は先ほどのNone-Selector Serviceと同じなので省略します。

なんとか動かせましたね。

まとめ

OKD(Kubernetes)のクラスタ外にあるリソースへアクセスするService、None-Selector ServiceとExternalName Serviceを
試してみました。

ちょっと気になる機能だったので、試してみて正解でした。最初、None-Selector Serviceがちょっとわかりにくかったです…。