LoginSignup
4
5

More than 3 years have passed since last update.

KubernetesでDNS名でのアクセスを操る方法

Posted at

概要

Kubernetesを利用した開発において、クラスタ外のDNS名(ドメイン名)を使ったアクセスが必要となる時があります。実際にそのDNS名でアクセスできる(しても良い)場合は問題ないのですが、例えば下記のようなケースが考えられます。

  • プログラムに本番用URLがハードコーディングされているが、何とかして開発用のMockにリクエストをねじ曲げたい
  • Cloud上のクラスタからしかアクセスできないManagedなDBに対してローカルのMacから接続したい

KubernetesにはDNS名でのアクセスを操作するための機能がいくつか用意されているため、これらのユースケースを実現できないかと検討してみたところ、標準機能に一工夫すれば実現できることが分かりました。

そこで検討したKubernetesの標準機能と、それを利用して上記のユースケースを実現する方法についてまとめてみました。

KubernetesのDNS関連機能

KubernetesのDNSに関する機能のうち、今回のユースケースに関係するもの1を記載しています。

(前提)ServiceへのDNS名でのアクセス

説明の前提として、ServiceへのDNS名でのアクセス方法について簡単に記載しておきます。

下記のようなServiceがあったとします。

svc1.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc1
sepc:
  ...

クラスタ内からは、このServiceに対して svc1 という名前でアクセスすることができます。

$ curl http://svc1/

正式には svc1.[namespace].svc.cluster.local という名前なのですが、クラスタ内では resolv.conf に下記のような search 行が自動設定されているため svc1 が補完されてアクセス可能になっています。

search [namespace].svc.cluster.local svc.cluster.local cluster.local

hostAliases

Podの /etc/hosts を上書きする機能です。

下記のように hostAlaises の設定をPodに対して行います。

alias.yaml
apiVersion: v1
kind: Pod
metadata:
  name: alias
spec:
  containers:
    - name: nginx
      image: nginx
  hostAliases:
    - ip: "127.0.0.1"
      hostnames:
        - "hoge.example.com"
        - "fuga.example.com"

設定されたPodから hoge.example.comfuga.example.com という名前でアクセスすると、実際には127.0.0.1(localhost) に(名前解決されて)リクエストが送られます。

root@alias:/# curl -s http://hoge.example.com/ | head
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }

これは最初に示したように Pod の /etc/hosts に下記のような追記が行われることにより実現されています。

root@alias:/# cat /etc/hosts | tail -n 2
# Entries added by HostAliases.
127.0.0.1       hoge.example.com        fuga.example.com

ExternalName

Service名に対して CNAME を設定できる機能です。

この機能を利用するには spec.typeExternalName な Service を作成します。

external-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: external-svc1
spec:
  type: ExternalName
  externalName: example.com

クラスタ内で external-svc1 という名前でアクセスすると、実際には example.com にリクエストが送られます2

$ curl -s -H 'Host: example.com' http://external-svc1/ | head
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {

(おまけ)Headless Service (subdomain)

今回のユースケースでは使っていない3のですがDNS関連の機能なようなので記載しておきます。Headless Service自体にはいくつかの使い方があるようですが、ここではDNS名(サブドメイン)に関連した部分のみ触れています。

まずはServiceの接続先となるPodを作成します。

sub-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sub-pod
  labels:
    name: sub
spec:
  hostname: sub1
  subdomain: example
  containers:
  - name: nginx
    image: nginx

Podのポイントは下記の点です。

  • hostnamesubdomain を設定する
  • Serviceのselectorに使うため metadata.labels.name=sub を設定する

このPodに対応するServiceを作成します。

sub-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: example
spec:
  selector:
    name: sub
  clusterIP: None

Serviceのポイントは下記の点です。

  • Podの subdomain とServiceの metadata.name を同じ名称にする。
  • clusterIPNone を指定する(このような Service を Headless Service と呼ぶらしいです)
  • spec.selector にPodの metadata.labels と同じ値を指定(これは普通のServiceと同じ)

このようなServiceを作成することでクラスタ内から sub1.example という名称で対象Podへのアクセスが可能となります。

root@alias:/# curl -s sub1.example | head
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }

ちなみに(こちらが普通の使い方だとは思いますが) example というServiceの名称でも同様にPodへのアクセスが可能です。

参考: ServiceとPodに対するDNS

ユースケースごとの実現方法

これまでに説明した標準機能の応用として、最初に示したユースケースなどの実現方法を示していきます。

Podからの外部DNS名でのアクセスを別の場所に向けたい

プログラム内で接続先に hoge.example.com がハードコーディングされている状況で、これを透過的に開発用のService(Pod)にアクセスさせたい等のケースです(プログラムを直した方が良い気はとてもしますが・・・)。

一見すると hostAliases だけで実現できそうなのですが、接続先のService(Pod)のIPアドレスは動的に変わるため、固定のIPアドレスを設定することは現実的ではないと思います。

この問題を解決する方法はいくつかあるようなのですが socat を併用する方法が一番簡単そうです。
具体的には下記のような構成で実現します。

まず接続元Podで hostAliases を使って hoge.example.com127.0.0.1 にaliasします。

  hostAliases:
    - ip: "127.0.0.1"
      hostnames:
        - "hoge.example.com"

次に接続元PodのSidecarとして socat を追加します。

  containers:
    - name: socat
      image: alpine/socat
      command: ["socat"]
      args:
        - "tcp-listen:8080,fork,reuseaddr"
        - "tcp-connect:mock-service:8080"
      ports:
        - containerPort: 8080

ここでのポイントは下記の点となります。

  • tcp-listenで接続先のポート番号を指定(ここでは 8080
  • tcp-connectで実際に接続させたいアドレスを指定(ここでは mock-service:8080

これで接続元Podから hoge.example.com:8080 という名称でアクセスすると開発用の mock-service:8080 に接続が向かう状態となります。

外部DNS名のリソースにPort Forwardingしたい

Kubernetesクラスタ内からしかアクセスできないリソースに対して、ローカルの端末からPort Forwardingして接続するようなケースです。例えばAWSのEKSクラスタと同じVPC内にあるDBに対して、ローカルMac上のDBツールを使って接続する場合などが該当します。

Serviceに対するPort Forwardingが可能なため、一見すると ExternalName で実現できそうなのですが、残念ながら ExternalName に対しては PortForwarding をすることはできないようです。

物理サーバで良くあるSSHの踏み台サーバ(Pod)を用意する方法も考えられるのですが、鍵の管理など色々と面倒な部分が出てきます。そこでこちらのケースでも socat を利用した下記のような構成で実現してみます。

まずPort Forwardingの対象となるServiceを用意します。

mysql.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mysql-proxy
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

次にServiceに対応する Deployment を作成します。(Podでも良いのですが死んだ場合に自動で再起動することを期待してDeploymentにしてみます)。このDeploymentではポート 3306managed-mysql:3306 に転送するsocatが動いています。

mysql-proxy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-proxy
spec:
  selector:
    matchLabels:
      app: mysql-proxy
  template:
    metadata:
      labels:
        app: mysql-proxy
    spec:
      containers:
        - name: socat
          image: alpine/socat
          command: ["socat"]
          args:
            - "tcp-listen:3306,fork,reuseaddr"
            - "tcp-connect:managed-mysql:3306"
          ports:
            - containerPort: 3306

最後に managed-mysql に対応する ExternalName を作成します。socatに直接RDBのアドレスを書いても良いのですが、実用的にはクラスタ上の他のPodからも利用することが多いと思われるのでExternalNameを介しています。

managed-mysql.yaml
apiVersion: v1
kind: Service
metadata:
  name: managed-mysql
spec:
  type: ExternalName
  externalName: [実際のRDBのアドレス]

あとは接続前に svc/mysql への Port Forwarding を実施すれば準備は完了です。

$ kubectl port-forward svc/mysql 3306:3306
Forwarding from 127.0.0.1:3306 -> 3306
Forwarding from [::1]:3306 -> 3306

127.0.0.1:3306 を経由してCloud上のDBへのアクセスが可能となります。

$ mysql -h 127.0.0.1 -P 3306 ...

Webブラウザから外部DNS名でクラスタ内のServiceに接続したい

Ingressを使わずに hoge.example.com という名称でWebブラウザからKubernetes上のService(Pod)にアクセスしたい場合が該当します。

主にProxy(Reverse Proxy)の設定方法になってしまうので詳細は省略しますが、下記のようにクラスタ上に Proxy(ForwardProxy + hostAliases + ReverseProxy) を用意して Port Forwarding した上でWebブラウザのProxy設定をそちらに向ければ実現できます。


  1. ここで触れているもの以外では /etc/resolv.conf の nameserver を変更して自前のDNSサーバを利用する機能などがあるようです。 

  2. そのままだとリクエストのHostヘッダが Host: external-svc1 になり意図したページが表示されないため -H オプションでHostヘッダを上書きしています。 

  3. 使いどころが今ひとつ分からなかったのですが、どのような場面で使う機能なんでしょうね? 

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5