2022/02/28
本記事ではF5社のBIG-IPとKDDIの事業用クラウド基盤上のKubernetesの連携について、検証結果を紹介します。KDDIでは事業用クラウド基盤にてKubernetesを容易に管理、展開可能な機能を提供しています。この機能は利用者に対してKubernetesクラスタの提供を行うものとなっています、今回はこの基盤をF5社が提供するLoadBalancer(以下、LB)であるBIG-IPとより簡単に連携させることを目指します。 検証はF5社が提供しているBIG-IPのF5 Container Ingress Service(以下、F5 CIS)プラグインを用いて、BIG-IPとKubernetesを連携させることにより行います。これにより外部からKubernetes内のPodにアクセスする際、BIG-IPによりL4レイヤーでアクセス分散させることが容易にできるようになります。本記事は事業用クラウド基盤を用いた検証となっていますが、F5 CISプラグインは様々なクラウド環境で使用できる汎用的なプラグインであり、事業用クラウド基盤外でも利用することができます。
Kubernetesは外部のLBと連携するためのインターフェースを備えており、Kubernetes内でリソースを作成することにより外部LBを操作し、Podへ通信を中継することが可能です。この機能を実現するためには、各LBベンダが開発している連携用プラグインを別途インストールする必要があります。 BIG-IPにおけるプラグインはF5 CISという名称で提供されています。このプラグインを導入することにより、Kubernetes側でLBリソースを作成し、BIG-IP側にもVirtualServerの設定を自動で作成することができるようになります。今回の検証ではKubernetesリソースのうち、TypeLoadbalancerリソースを利用した通信設定の検証を行います。
F5 CISプラグインを導入するとF5 CIS、BIG-IP、通信先のPodは図1のような構成をとります。
Kubernetes上でのリソース作成時、F5 CISのPodが作成を検知し、BIG-IPのAPIを実行することでVirtualServerを作成します。通信先のPodに再作成や数の増減が起こったときやリソースが削除されたときも同様に変化を検知し、自動でBIG-IPに対し転送先の変更や設定の削除を行います。 リソースが作成された後はBIG-IPのVirtualServer経由でKubernetes内のPodへ通信できるようになります。この時、F5 CISならではの特徴として、BIG-IPがKubernetesのCNIによって構成されたOverlay NWに直接接続し、KubernetesのService NWを経由せず直接Podへ通信するようになっています。この特徴によりKubernetesのServiceのLB機能を迂回するため、レイテンシや負荷を抑えることが可能になっています。
ここからはF5 CISプラグインを導入する方法について記載していきます。導入は大きく分けると以下の2ステップに分かれます。
まずBIG-IPをKubernetesの仮想NWに接続する手順について説明していきます。 接続方法はKubernetesで利用しているCNIプラグインによって異なり、今回はCanalを用いることを前提として進めます。CanalプラグインはFlannelとCalicoを組み合わせたプラグインであるためFlannelと同じ方法で設定できます。 最初はBIG-IPの管理コンソールにて[Network] -> [Tunnels] -> [Profiles] -> [VXLAN] -> [Create]を開き、vxlan設定を作成します。作成時は以下の内容を入力します。
項目 | 内容 |
---|---|
名前 | fl-vxlan |
Parent Profile | vxlan |
Port | 8472 |
Flooding Type | None |
次に[Network] -> [Tunnels] -> [Tunnel list] -> [Create] と遷移し、トンネルインターフェースを作成します。作成時は以下の内容を入力します。
項目 | 内容 |
---|---|
名前 | fl-tunnel |
key | 1 |
profile | fl-vxlan |
local address | BIG-IPのKubernetes内部通信用NWのIP |
MTU | 1450 |
Use PMTU | チェックを外す |
最後に[Network] -> [Self IPs] -> [Create] でトンネルインターフェース上に仮想インターフェースを作成します。設定内容は以下を入力します。
項目 | 内容 |
---|---|
名前 | fl-vxlan-selfip |
IP Address | CanalのNWのうちまだ使われていないNWのIPアドレス 例:10.42.255.0/24 |
Netmask | 255.255.0.0 |
VLAN / Tunnel | fl-tunnel |
Port Lockdown | Allow all |
以上でNWインターフェースの作成は完了です。 続いてKubernetes側の操作でBIG-IPをKubernetes上のノードとして登録します。以下のリソースを作成し、KubernetesにApplyします。
apiVersion: v1 kind: Node metadata: name: bigip1 annotations: flannel.alpha.coreos.com/backend-data: '{"VtepMAC":"$MAC_ADDRESS"}' #fl-vxlan-selfipのインターフェースのMACアドレスを入力 flannel.alpha.coreos.com/backend-type: "vxlan" flannel.alpha.coreos.com/kube-subnet-manager: "true" flannel.alpha.coreos.com/public-ip: $local_address #fl-tunnelのLocal Addressを入力 node.alpha.kubernetes.io/ttl: "0" projectcalico.org/IPv4Address: $local_address/24 #fl-tunnelのLocal Addressを入力 projectcalico.org/IPv4IPIPTunnelAddr: $fl_vxlan_selfip #fl-vxlan-selfipのIP Addressを入力 rke.cattle.io/external-ip: $local_address #fl-tunnelのLocal Addressを入力 rke.cattle.io/internal-ip: $local_address #fl-tunnelのLocal Addressを入力 cattle.rancher.io/node-status: ignore volumes.kubernetes.io/controller-managed-attach-detach: "true" spec: podCIDR: $fl_vxlan_selfip/24 #fl-vxlan-selfipのIP Addressを入力 podCIDRs: - $fl_vxlan_selfip/24 #fl-vxlan-selfipのIP Addressを入力
Apply後kubectlコマンドでノード一覧を表示し該当ノードが表示されれば成功です。表示上はNotReadyになっていますが、これはBIG-IPにkubeletが入っておらずKubernetesからノードの死活を確認できないためで、仕様通りの動作となります。この設定完了後BIG-IPのトンネルインターフェースへのルーティング設定がKubernetesの各ノードに通知され、Kubernetes上のOverlay NWを通してPodとBIG-IPが直接通信可能になります。 次はF5 CISプラグインのインストールです。まずBIG-IP側でF5 CISからのAPI操作を受け付けられるように、F5 Application Services 3 Extension(以下、AS3)プラグインをインストールします。以下サイトよりrpmファイルをDLし、BIG-IP管理コンソールの[iApps] -> [Package Management LX]よりrpmファイルをインポートします。 https://github.com/F5Networks/f5-appsvcs-extension/releases/ また、F5 CISが動作するためにはプラグインが作成するVirtualServerを配置するPartitionが必要です。そのため、[System] -> [Users] -> [Partition List]よりPartitionを新規作成し、kubernetesという名前のパーティションを作成します。
続いてKubernetes側の設定を行なっていきます。まずF5 CISプラグインがKubernetesを操作するための認証設定を作成します。
kubectl create secret generic bigip-login -n kube-system --from-literal=username=$id --from-literal=password=$password # $id, $passwordはBIG-IPのIDとPWを入力 kubectl create serviceaccount bigip-ctlr -n kube-system
kubectl apply -f k8s_rbac.yamlk8s_rbac.yaml
# for use in k8s clusters only kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: bigip-ctlr-clusterrole rules: - apiGroups: ["", "extensions", "networking.k8s.io"] resources: ["nodes", "services", "endpoints", "namespaces", "ingresses", "pods", "ingressclasses"] verbs: ["get", "list", "watch"] - apiGroups: ["", "extensions", "networking.k8s.io"] resources: ["configmaps", "events", "ingresses/status", "services/status"] verbs: ["get", "list", "watch", "update", "create", "patch"] - apiGroups: ["cis.f5.com"] resources: ["virtualservers","virtualservers/status", "tlsprofiles", "transportservers", "ingresslinks", "externaldnss"] verbs: ["get", "list", "watch", "update", "patch"] - apiGroups: ["fic.f5.com"] resources: ["f5ipams", "f5ipams/status","ipams"] verbs: ["get", "list", "watch", "update", "create", "patch", "delete"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch", "update", "create", "patch"] - apiGroups: ["", "extensions"] resources: ["secrets"] verbs: ["get", "list", "watch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: bigip-ctlr-clusterrole-binding namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: bigip-ctlr-clusterrole subjects: - apiGroup: "" kind: ServiceAccount name: bigip-ctlr namespace: kube-system
作成が終わったらF5 CISがデータ管理のために使うCustomResourceDefinitionを作成します。以下のマニフェストを適用することで作成できます
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: virtualservers.cis.f5.com spec: group: cis.f5.com names: kind: VirtualServer plural: virtualservers shortNames: - vs singular: virtualserver scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: host: type: string pattern: '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' httpTraffic: type: string ipamLabel: type: string snat: type: string tlsProfileName: type: string rewriteAppRoot: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9]+\/?)*$' waf: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9]+\/?)*$' allowVlans: items: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9-_]+\/?)*$' type: array iRules: type: array items: type: string serviceAddress: type: array maxItems: 1 items: type: object properties: arpEnabled: type: boolean icmpEcho: type: string enum: [enable, disable, selective] routeAdvertisement: type: string enum: [enable, disable, selective, always, any, all] spanningEnabled: type: boolean trafficGroup: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9]+\/?)*$' pools: type: array items: type: object properties: path: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9]+\/?)*$' service: type: string pattern: '^([A-z0-9-_+])*([A-z0-9])$' nodeMemberLabel: type: string pattern: '^[a-zA-Z0-9][-A-Za-z0-9_.]{0,61}[a-zA-Z0-9]=[a-zA-Z0-9][-A-Za-z0-9_.]{0,61}[a-zA-Z0-9]$' servicePort: type: integer minimum: 1 maximum: 65535 rewrite: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9]+\/?)*$' monitor: type: object properties: type: type: string enum: [http, https] send: type: string recv: type: string interval: type: integer timeout: type: integer required: - type - send - interval virtualServerAddress: type: string pattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' virtualServerName: type: string pattern: '^([A-z0-9-_+])*([A-z0-9])$' virtualServerHTTPPort: type: integer minimum: 1 maximum: 65535 virtualServerHTTPSPort: type: integer minimum: 1 maximum: 65535 status: type: object properties: vsAddress: type: string additionalPrinterColumns: - name: host type: string description: hostname jsonPath: .spec.host - name: tlsProfileName type: string description: TLS Profile attached jsonPath: .spec.tlsProfileName - name: httpTraffic type: string description: Http Traffic Termination jsonPath: .spec.httpTraffic - name: IPAddress type: string description: IP address of virtualServer jsonPath: .spec.virtualServerAddress - name: ipamLabel type: string description: ipamLabel for virtual server jsonPath: .spec.ipamLabel - name: IPAMVSAddress type: string description: IP address of virtualServer jsonPath: .status.vsAddress - name: Age type: date jsonPath: .metadata.creationTimestamp subresources: status: {} --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: tlsprofiles.cis.f5.com spec: group: cis.f5.com names: kind: TLSProfile plural: tlsprofiles shortNames: - tls singular: tlsprofile scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: hosts: type: array items: type: string pattern: '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' tls: type: object properties: termination: type: string enum: [edge, reencrypt, passthrough] clientSSL: type: string serverSSL: type: string reference: type: string required: - termination --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: transportservers.cis.f5.com spec: group: cis.f5.com names: kind: TransportServer plural: transportservers shortNames: - ts singular: transportserver scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: virtualServerAddress: type: string pattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' virtualServerPort: type: integer minimum: 1 maximum: 65535 virtualServerName: type: string pattern: '^([A-z0-9-_+])*([A-z0-9])$' mode: type: string enum: [standard, performance] type: type: string enum: [tcp, udp] snat: type: string allowVlans: items: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9-_]+\/?)*$' type: array iRules: type: array items: type: string ipamLabel: type: string serviceAddress: type: array maxItems: 1 items: type: object properties: arpEnabled: type: boolean icmpEcho: type: string enum: [enable, disable, selective] routeAdvertisement: type: string enum: [enable, disable, selective, always, any, all] spanningEnabled: type: boolean trafficGroup: type: string pattern: '^\/([A-z0-9-_+]+\/)*([A-z0-9]+\/?)*$' pool: type: object properties: service: type: string pattern: '^([A-z0-9-_+])*([A-z0-9])$' servicePort: type: integer minimum: 1 maximum: 65535 monitor: type: object properties: type: type: string enum: [tcp, udp] interval: type: integer timeout: type: integer required: - type - interval required: - service - servicePort required: - virtualServerPort - pool additionalPrinterColumns: - name: virtualServerAddress type: string description: IP address of virtualServer jsonPath: .spec.virtualServerAddress - name: virtualServerPort type: integer description: Port of virtualServer jsonPath: .spec.virtualServerPort - name: pool type: string description: Name of service jsonPath: .spec.pool.service - name: poolPort type: string description: Port of service jsonPath: .spec.pool.servicePort - name: Age type: date jsonPath: .metadata.creationTimestamp --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: externaldnss.cis.f5.com spec: group: cis.f5.com names: kind: ExternalDNS plural: externaldnss shortNames: - edns singular: externaldns scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: domainName: type: string pattern: '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' dnsRecordType: type: string pattern: 'A' loadBalanceMethod: type: string pools: type: array items: type: object properties: name: type: string pattern: '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' dataServerName: type: string dnsRecordType: type: string pattern: 'A' loadBalanceMethod: type: string monitor: type: object properties: type: type: string enum: [http, https] send: type: string recv: type: string interval: type: integer timeout: type: integer required: - type - send - interval required: - name - dataServerName required: - domainName additionalPrinterColumns: - name: domainName type: string description: Domain name of virtual server resource jsonPath: .spec.domainName - name: Age type: date jsonPath: .metadata.creationTimestamp --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: ingresslinks.cis.f5.com spec: group: cis.f5.com names: kind: IngressLink shortNames: - il singular: ingresslink plural: ingresslinks scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: virtualServerAddress: type: string pattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' ipamLabel: type: string iRules: type: array items: type: string selector: properties: matchLabels: additionalProperties: type: string type: object type: object status: type: object properties: vsAddress: type: string additionalPrinterColumns: - name: IPAMVSAddress type: string description: IP address of virtualServer jsonPath: .status.vsAddress - name: Age type: date jsonPath: .metadata.creationTimestamp subresources: status: { }
以上でプラグイン導入の下準備は完了です。いよいよ実際にプラグインをインストールします。インストールのために以下2つのDeploymentを導入する必要があります。
まずF5 IPAM Controllerからインストールを行います。以下のマニフェストを適用することでインストールできます。
# Sample configuration for f5-ipam-controller with default provider. For persistent IP addresses upon restarts, # volume mounts are used. securityContext is used to change mount permissions to controller user. apiVersion: apps/v1 kind: Deployment metadata: labels: name: f5-ipam-controller name: f5-ipam-controller namespace: kube-system spec: replicas: 1 selector: matchLabels: app: f5-ipam-controller template: metadata: labels: app: f5-ipam-controller spec: containers: - args: - --orchestration - kubernetes - --ip-range - '{"$pool_name":"$ip_address_range"}' #$pool_nameは任意の名前、$ip_address_rangeはVirtualServerにアサインしたいIPアドレスの範囲を入力 - --log-level - DEBUG command: - /app/bin/f5-ipam-controller image: f5networks/f5-ipam-controller:0.1.4 imagePullPolicy: IfNotPresent name: f5-ipam-controller terminationMessagePath: /dev/termination-log securityContext: fsGroup: 1200 runAsGroup: 1200 runAsUser: 1200 serviceAccount: bigip-ctlr serviceAccountName: bigip-ctlr
続いてF5 CISをインストールします。以下のマニフェストを適用することでインストールできます。
apiVersion: apps/v1 kind: Deployment metadata: name: k8s-bigip-ctlr-deployment-1 namespace: kube-system spec: # DO NOT INCREASE REPLICA COUNT replicas: 1 selector: matchLabels: app: k8s-bigip-ctlr-deployment-1 template: metadata: labels: app: k8s-bigip-ctlr-deployment-1 spec: # Name of the Service Account bound to a Cluster Role with the required # permissions containers: - name: k8s-bigip-ctlr image: "f5networks/k8s-bigip-ctlr:2.4.1" env: - name: BIGIP_USERNAME valueFrom: secretKeyRef: # Replace with the name of the Secret containing your login # credentials name: bigip-login key: username - name: BIGIP_PASSWORD valueFrom: secretKeyRef: # Replace with the name of the Secret containing your login # credentials name: bigip-login key: password command: ["/app/bin/k8s-bigip-ctlr"] args: [ # See the k8s-bigip-ctlr documentation for information about # all config options # https://clouddocs.f5.com/containers/latest/ "--bigip-username=$(BIGIP_USERNAME)", "--bigip-password=$(BIGIP_PASSWORD)", "--bigip-url=https://$BIG-IP_URL", #$BIG-IP_URLにはBIG-IPの管理コンソールのURLを入力 "--bigip-partition=kubernetes", "--flannel-name=/Common/fl-tunnel", "--custom-resource-mode=true", "--pool-member-type=cluster", "--ipam=true", "--insecure", "--log-as3-response=true" ] serviceAccountName: bigip-ctlr
動作確認のためにType Loadbalancerリソースを作成してみます。 以下のようなマニフェストを適用し、nginxとType Loadbalancerリソースを作成してみます。
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 # tells deployment to run 2 pods matching the template template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-ingress annotations: cis.f5.com/ipamLabel: test #F5 IPAM Controller作成時に指定したpool_nameを入力 spec: loadBalancerIP: 192.168.1.201 #F5 IPAM Controller作成時に指定した範囲内のIPアドレスを入力 externalTrafficPolicy: Local type: LoadBalancer ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app: nginx
作成後、BIG-IP側にVirtualServerが作成されていれば成功です。
本記事ではF5 CISプラグインを用いたKubernetesとBIG-IPの連携検証についてまとめました。 検証の結果、プラグインは事業用クラウド上のKubernetesでも問題なく動作可能であることがわかりました。また、動作における環境的制約が少なく設定も難しくないため、様々な環境で利用できるプラグインと言えます。 本検証により、KDDI社内でもLBとして広く使われるF5社のBIG-IPをKubernetesクラスタ上のコンテナ向けにも簡易に使えるようになり、基盤上でBIG-IPを選択肢としやすくすることを実現しました。