目次
1従来のマイクロサービスMicroServiceの問題:侵入型のクライアント側サービス検出+ LoadBalance
2 Istioはどのようにしてトラフィックハイジャックを達成しますか?
3質問:対象サービスの種類を判断するにはどうすればよいですか?
1従来のマイクロサービスMicroServiceの問題:侵入型のクライアント側サービス検出+ LoadBalance
1.1クライアント側のサービス検出+負荷分散
従来のマイクロサービスであるサービス検出+負荷分散コードは、ビジネスコードと結合され、運用中のビジネスと同じプロセスで実行されます。
たとえば、Springbootプロジェクトによって開始されたTomcatサービス、ビジネスロジックはこのTomcatで実行され、サービス検出コードとサービス検出後の負荷分散コードもこのTomcatで実行されます。
では、ビジネスコードとフレームワークコードを切り離すことができますか?
tomcatサーバーでビジネスコードのみが実行され、サービス検出と負荷分散が他のプロセスに引き渡されることを理解できますか?
答えは「はい」です。サービス検出と負荷分散を別のサイドカープロセスに配置し、ビジネスコードから切り離し、同時にトラフィックハイジャックを通じてサービストラフィックのプロキシを実現します。
Istioのプロジェクトのハイライトの1つは、コード行を変更することなく、古いアプリケーションをServiceMeshプラットフォームにシームレスに接続できることです。この機能を実現するために、現在、トラフィックは傍受され、iptablesを介してプロキシに転送されます。
2 Istioはどのようにしてトラフィックハイジャックを達成しますか?
Istioの実装を参照すると、簡単なトラフィックハイジャックスキームを自分で設計できます。
2.1何をする必要がありますか?
- まず、ハイジャックされたトラフィックを処理し、接続が確立されたときに元の宛先アドレスを取得できる透過プロキシをサポートするプロキシが必要です。k8sでは、このプロキシはサイドカー方式とトラフィックをハイジャックするサービスを使用してポッドにデプロイされます。
- iptablesを介してプロキシにハイジャックするトラフィックをハイジャックします。プロキシ自身のトラフィックは除外する必要があります。
- 侵入をゼロにするには、サービスのイメージを変更しないことをお勧めします.k8sでは、アプリケーションコンテナが起動する前に、Initコンテナを使用してiptablesを変更できます。
2.2透過プロキシ
透過プロキシとして、処理できるトラフィックは、再試行、タイムアウト、負荷分散などの一連の処理ロジックを経て、ピアサービスに転送されます。単独で処理できないトラフィックの場合、処理せずに直接透過的に送信されます。
トラフィックがiptablesを介してプロキシに転送された後、プロキシは接続が確立されたときに元の宛先アドレスを取得できる必要があります。Goでの実装はもう少し面倒で、次のsyscall
ように呼び出すことで取得する必要があります 。
サンプルコード:
package redirect
import (
"errors"
"fmt"
"net"
"os"
"syscall"
)
const SO_ORIGINAL_DST = 80
var (
ErrGetSocketoptIPv6 = errors.New("get socketopt ipv6 error")
ErrResolveTCPAddr = errors.New("resolve tcp address error")
ErrTCPConn = errors.New("not a valid TCPConn")
)
// For transparent proxy.
// Get REDIRECT package's originial dst address.
// Note: it may be only support linux.
func GetOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
fc, errRet := conn.File()
if errRet != nil {
conn.Close()
err = ErrTCPConn
return
} else {
conn.Close()
}
defer fc.Close()
mreq, errRet := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if errRet != nil {
err = ErrGetSocketoptIPv6
c, _ = getTCPConnFromFile(fc)
return
}
// only support ipv4
ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7])
port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3])
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port))
if err != nil {
err = ErrResolveTCPAddr
return
}
c, errRet = getTCPConnFromFile(fc)
if errRet != nil {
err = ErrTCPConn
return
}
return
}
func getTCPConnFromFile(f *os.File) (*net.TCPConn, error) {
newConn, err := net.FileConn(f)
if err != nil {
return nil, ErrTCPConn
}
c, ok := newConn.(*net.TCPConn)
if !ok {
return nil, ErrTCPConn
}
return c, nil
}
GetOriginalDstAddr
接続の元の宛先アドレスは、関数を介して 取得できます。
ここで重要なのは、iptables転送が有効になっている場合、プロキシが自身に直接アクセスする接続を受信すると、プロキシはそれを処理できないことを認識し、宛先アドレス(つまり、自身にバインドされたアドレス)に接続することです。 、それが無限のループにつながるように。したがって、サービスの開始時には、宛先アドレスが自身のIPである接続を直接切断する必要があります。
2.3サイドカー
サイドカーモードを使用してサービスグリッドをデプロイすると、各サービスの横に追加のプロキシが開かれ、コンテナのトラフィックの一部を引き継ぎます。kubernetesでは、ポッドに複数のコンテナを含めることができます。これらの複数のコンテナは、ネットワークやストレージなどのリソースを共有できます。概念的には、サービスコンテナとプロキシコンテナをポッドにデプロイします。プロキシコンテナはサイドカーコンテナに相当します。
デプロイメントを通じてデモンストレーションします。このデプロイメントのyaml構成には、ネットワークを共有する2つのコンテナーのテストとプロキシが含まれているため、テストコンテナーにログインした後、 127.0.0.1:30000
を介してプロキシコンテナーにアクセスできます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
namespace: default
labels:
app: test
spec:
replicas: 1
template:
metadata:
labels:
app: test
spec:
containers:
- name: test
image: {test-image}
ports:
- containerPort: 9100
- name: proxy
image: {proxy-image}
ports:
- containerPort: 30000
各サービスのサイドカーコンテナの設定を作成するのは面倒な作業です。アーキテクチャが成熟MutatingAdmissionWebhook
すると、ユーザーがデプロイを作成するときに、kubernetesの機能を使用してサイドカー関連の設定を アクティブに挿入できます。
たとえば、デプロイメントのアノテーションに次のフィールドを追加します。
annotations:
xxx.com/sidecar.enable: "true"
xxx.com/sidecar.version: "v1"
サイドカーのv1バージョンをこのデプロイメントに注入する必要があることを示します。サービスがこのWebhookを受信すると、関連するアノテーションフィールドを確認し、サイドカー構成を挿入するかどうか、およびフィールド構成に従って構成のバージョンを決定できます。サービスに応じて変更する必要のあるパラメーターがある場合は、これも使用できます。配信方法により、柔軟性が大幅に向上します。
2.4 iptables
iptablesを介して、指定されたトラフィックをプロキシにハイジャックし、一部のトラフィックを除外できます。
iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 9527 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d 172.17.0.0/16 -j REDIRECT --to-port 30000
上記のコマンドは、ターゲットアドレス を172.17.0.0/16
持つトラフィック REDIRECT
をポート30000(プロキシが監視するポート)に転送することを意味し ます。ただし、UID9527で開始されたプロセスは例外です。172.17.0.0/16
このアドレスは、k8sクラスター内のIPセグメントです。トラフィックのこの部分のみをハイジャックする必要があります。クラスターの外部にアクセスするトラフィックの場合、一時的にハイジャックすることはありません。すべてのトラフィックがハイジャックされた場合、プロキシが処理できない場合は、iptablesルールで除外する必要があります。
2.5初期化コンテナ
前述のように、侵入をゼロにするには、ユーザーサービスコンテナーを開始する前に、Initコンテナーを使用してiptablesを変更する必要があります。構成のこの部分は、kubernetesのMutatingAdmissionWebhook
機能を介してユーザーのデプロイメント構成に挿入することもでき ます。
Initコンテナの構成を前のサイドカーの構成に追加します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
namespace: default
labels:
app: test
spec:
replicas: 1
template:
metadata:
labels:
app: test
spec:
initContainers:
- name: iptables-init
image: {iptables-image}
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 9527 -j RETURN && iptables -t nat -A OUTPUT -p tcp -d 172.17.0.0/16 -j REDIRECT --to-port 30000']
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
containers:
- name: test
image: {test-image}
ports:
- containerPort: 9100
- name: proxy
image: {proxy-image}
ports:
- containerPort: 30000
このInitコンテナはiptablesをインストールする必要があり、設定したiptablesコマンドは起動時に実行されます。
securityContext
この構成アイテムには特に注意が必要 NET_ADMIN
です。権限を追加しまし た。ポッドまたはコンテナの権限を定義するために使用されます。設定されていない場合、iptablesはコマンドの実行時にエラーを表示します。
3質問:対象サービスの種類を判断するにはどうすればよいですか?
私たちは 172.17.0.0/16
、プロキシへのすべてのトラフィックをハイジャックし、そのターゲット・サービスのプロトコルタイプを決定するためにどのように?プロトコルタイプがわからない場合、後続のリクエストを解析する方法がわかりません。
kubernetesサービスでは、各サービスのポートの名前を指定できます。この名前の形式は{name}-{protocol}
、たとえば {test-http}
、このサービスの特定のポートがhttpプロトコルであるように固定でき ます。
kind: Service
apiVersion: v1
metadata:
name: test
namespace: default
spec:
selector:
app: test
ports:
- name: test-http
port: 9100
targetPort: 9100
protocol: TCP
プロキシは、検出サービスを介してサービスに対応するクラスターIPとポート名を取得するため、接続の通信プロトコルタイプは、ターゲットサービスのIPとポートを介して認識され、対応するハンドラーに渡されます。処理用。
3.1クラスターIP
kubernetesでサービスを作成します。指定されていない場合、デフォルトでクラスターIPがアクセスに使用されます。Kube-proxyはこのためのiptablesルールを作成し、クラスターIPを負荷分散に変換してポッドIPに転送します。
クラスターIPがある場合、サービスのDNS解決はクラスターIPを指し、負荷分散はiptablesによって行われます。存在しない場合、DNS解決の結果はポッドIPを直接指します。
プロキシは、サービスのクラスターIPに依存して、ユーザーがアクセスしているサービスを判別するため、に設定することはできません clusterIP: None
。ポッドIPは頻繁に変更される可能性があるため、インスタンスを追加または削除すると、ポッドIPのセットが変更され、プロキシはこれらの変更をリアルタイムで取得できません。