# Install Istio Multicluster Replicated Control Plane

> Canonical: https://zufardhiyaulhaq.com/istio-multicluster-replicated-control-plane/
> Tags: [istio, kubernetes]

Istio is a service mesh platform that can control and modify traffic policy behaviour in Kubernetes by injecting sidecar to a container. Multicluster Replicated Control Plane is an uses cases to enable communication between two service in two difference service meshes without using Ingress and can enable mutual TLS between the service.

Multicluster Replicated Control Plane allow your service to communicate each other with `*.global` FQDN between service mesh network. This tutorial is written because the original installation documentation is sucks lol (theory behind it is good).

### Requirement
- virtualbox
- minikube
- kubectl
- istioctl 1.4.9 or 1.5.4
- [step and step-ca](https://smallstep.com/docs/getting-started/#1-installing-step-and-step-ca)

### Bootstraping two Kubernetes clusters

Use minikube to start two kubernetes cluster named `cluster1` and `cluster2`.
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">minikube start <span class="nt">--driver</span><span class="o">=</span>virtualbox <span class="nt">--force</span><span class="o">=</span><span class="nb">true</span> <span class="nt">--kubernetes-version</span><span class="o">=</span><span class="s1">'1.15.11'</span> <span class="nt">--memory</span> 16128 <span class="nt">--cpus</span> 8 <span class="nt">--profile</span> cluster1
minikube start <span class="nt">--driver</span><span class="o">=</span>virtualbox <span class="nt">--force</span><span class="o">=</span><span class="nb">true</span> <span class="nt">--kubernetes-version</span><span class="o">=</span><span class="s1">'1.15.11'</span> <span class="nt">--memory</span> 16128 <span class="nt">--cpus</span> 8 <span class="nt">--profile</span> cluster2</code></pre></figure>
*Notes: please adjust with your environment*

### Generate Certificate with step and step-ca
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">step certificate create zufar-root-ca root-cert.pem root-key.pem <span class="nt">--profile</span> root-ca  <span class="nt">--kty</span> RSA <span class="nt">--no-password</span> <span class="nt">--insecure</span> <span class="nt">--not-after</span> 87600h <span class="nt">--san</span> zufardhiyaulhaq.com
step certificate create zufar-intermediate-ca ca-cert.pem ca-key.pem <span class="nt">--profile</span> intermediate-ca <span class="nt">--kty</span> RSA <span class="nt">--ca</span> ./root-cert.pem <span class="nt">--ca-key</span> ./root-key.pem <span class="nt">--no-password</span> <span class="nt">--insecure</span> <span class="nt">--not-after</span> 43800h <span class="nt">--san</span> zufardhiyaulhaq.com
step certificate bundle ca-cert.pem root-cert.pem cert-chain.pem</code></pre></figure>

This will create root certificate, intermediate certificate, and certificate chain.

### Add Kiali environment
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">KIALI_USERNAME</span><span class="o">=</span><span class="si">$(</span><span class="nb">read</span> <span class="nt">-p</span> <span class="s1">'Kiali Username: '</span> uval <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="nt">-n</span> <span class="nv">$uval</span> | <span class="nb">base64</span><span class="si">)</span>
<span class="nv">KIALI_PASSPHRASE</span><span class="o">=</span><span class="si">$(</span><span class="nb">read</span> <span class="nt">-sp</span> <span class="s1">'Kiali Passphrase: '</span> pval <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="nt">-n</span> <span class="nv">$pval</span> | <span class="nb">base64</span><span class="si">)</span>
<span class="nv">NAMESPACE</span><span class="o">=</span>istio-system</code></pre></figure>

## Install Istio

Istioctl 1.4.9 use CRD named `IstioControlPlane` to create the cluster. We will use this CRD:
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">install.istio.io/v1alpha2</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">IstioControlPlane</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">coreDNS</span><span class="pi">:</span>
    <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
  <span class="na">values</span><span class="pi">:</span>
    <span class="na">istiocoredns</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">kiali</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">global</span><span class="pi">:</span>
      <span class="na">podDNSSearchNamespaces</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">global</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">valueOrDefault</span><span class="nv"> </span><span class="s">.DeploymentMeta.Namespace</span><span class="nv"> </span><span class="se">\"</span><span class="s">default</span><span class="se">\"</span><span class="nv"> </span><span class="s">}}.global"</span>
      <span class="na">multiCluster</span><span class="pi">:</span>
        <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">controlPlaneSecurityEnabled</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">security</span><span class="pi">:</span>
      <span class="na">selfSigned</span><span class="pi">:</span> <span class="no">false</span>
    <span class="na">gateways</span><span class="pi">:</span>
      <span class="na">istio-ingressgateway</span><span class="pi">:</span>
        <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">NodePort</span></code></pre></figure>

For Istio 1.5.4, use `IstioOperator`
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">install.istio.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">IstioOperator</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">components</span><span class="pi">:</span>
    <span class="na">pilot</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">k8s</span><span class="pi">:</span>
        <span class="na">replicaCount</span><span class="pi">:</span> <span class="m">1</span>
        <span class="na">hpaSpec</span><span class="pi">:</span>
          <span class="na">maxReplicas</span><span class="pi">:</span> <span class="m">3</span>
          <span class="na">minReplicas</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">addonComponents</span><span class="pi">:</span>
    <span class="na">ingressGateways</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">k8s</span><span class="pi">:</span>
        <span class="na">replicaCount</span><span class="pi">:</span> <span class="m">1</span>
        <span class="na">hpaSpec</span><span class="pi">:</span>
          <span class="na">maxReplicas</span><span class="pi">:</span> <span class="m">3</span>
          <span class="na">minReplicas</span><span class="pi">:</span> <span class="m">1</span>
    <span class="na">prometheus</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">k8s</span><span class="pi">:</span>
        <span class="na">replicaCount</span><span class="pi">:</span> <span class="m">1</span>
    <span class="na">kiali</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">k8s</span><span class="pi">:</span>
        <span class="na">replicaCount</span><span class="pi">:</span> <span class="m">1</span>
    <span class="na">istiocoredns</span><span class="pi">:</span>
      <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">k8s</span><span class="pi">:</span>
        <span class="na">replicaCount</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">values</span><span class="pi">:</span>
    <span class="na">global</span><span class="pi">:</span>
      <span class="na">podDNSSearchNamespaces</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">global</span>
        <span class="pi">-</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">valueOrDefault</span><span class="nv"> </span><span class="s">.DeploymentMeta.Namespace</span><span class="nv"> </span><span class="se">\"</span><span class="s">default</span><span class="se">\"</span><span class="nv"> </span><span class="s">}}.global"</span>
      <span class="na">multiCluster</span><span class="pi">:</span>
        <span class="na">enabled</span><span class="pi">:</span> <span class="no">true</span>
      <span class="na">controlPlaneSecurityEnabled</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">security</span><span class="pi">:</span>
      <span class="na">selfSigned</span><span class="pi">:</span> <span class="no">false</span>
    <span class="na">gateways</span><span class="pi">:</span>
      <span class="na">istio-ingressgateway</span><span class="pi">:</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">NodePort</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">istio-ingressgateway</span></code></pre></figure>

`IstioControlPlane` change to `IstioOperator` in 1.5 release. This CRD install kiali and change ingressgateway into NodePort. please adjust with your environment. Save this file as `istio.yaml`.

### Install Istio in Cluster1

- Add cacerts certificate
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster1 create namespace istio-system
kubectl <span class="nt">--context</span> cluster1 create secret generic cacerts <span class="nt">-n</span> istio-system <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>ca-cert.pem <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>ca-key.pem <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>root-cert.pem <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>cert-chain.pem</code></pre></figure>
- Add kiali secret
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster1 apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
apiVersion: v1
kind: Secret
metadata:
  name: kiali
  namespace: </span><span class="nv">$NAMESPACE</span><span class="sh">
  labels:
    app: kiali
type: Opaque
data:
  username: </span><span class="nv">$KIALI_USERNAME</span><span class="sh">
  passphrase: </span><span class="nv">$KIALI_PASSPHRASE</span><span class="sh">
EOF</span></code></pre></figure>
- Install Istio
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">istioctl <span class="nt">--context</span> cluster1 manifest apply <span class="nt">-f</span> istio.yaml</code></pre></figure>
- Patch the coredns

This patch introduce a config to proxy `*.global` FQDN request from main `coredns` to `istiocoredns`.
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster1 apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           upstream
           fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        proxy . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
    global:53 {
        errors
        cache 30
        proxy . </span><span class="si">$(</span>kubectl <span class="nt">--context</span> cluster1 get svc <span class="nt">-n</span> istio-system istiocoredns <span class="nt">-o</span> <span class="nv">jsonpath</span><span class="o">={</span>.spec.clusterIP<span class="o">}</span><span class="si">)</span><span class="sh">
    }
EOF</span></code></pre></figure>

Do the same thing with other kubernetes cluster.

### Install Istio in cluster2

- Add cacerts certificate
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster2 create namespace istio-system
kubectl <span class="nt">--context</span> cluster2 create secret generic cacerts <span class="nt">-n</span> istio-system <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>ca-cert.pem <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>ca-key.pem <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>root-cert.pem <span class="se">\</span>
    <span class="nt">--from-file</span><span class="o">=</span>cert-chain.pem</code></pre></figure>
- Add kiali secret
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster2 apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
apiVersion: v1
kind: Secret
metadata:
  name: kiali
  namespace: </span><span class="nv">$NAMESPACE</span><span class="sh">
  labels:
    app: kiali
type: Opaque
data:
  username: </span><span class="nv">$KIALI_USERNAME</span><span class="sh">
  passphrase: </span><span class="nv">$KIALI_PASSPHRASE</span><span class="sh">
EOF</span></code></pre></figure>
- Install Istio
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">istioctl <span class="nt">--context</span> cluster2 manifest apply <span class="nt">-f</span> istio.yaml</code></pre></figure>
- Patch the coredns

This patch introduce a config to proxy `*.global` FQDN request from main `coredns` to `istiocoredns`.
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster2 apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           upstream
           fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        proxy . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
    global:53 {
        errors
        cache 30
        proxy . </span><span class="si">$(</span>kubectl <span class="nt">--context</span> cluster2 get svc <span class="nt">-n</span> istio-system istiocoredns <span class="nt">-o</span> <span class="nv">jsonpath</span><span class="o">={</span>.spec.clusterIP<span class="o">}</span><span class="si">)</span><span class="sh">
    }
EOF</span></code></pre></figure>

We have two Kubernetes cluster with istio enabled. This istio shared same root CA.

## Mutual TLS Testing

### Install httpbin service in Cluster2

- Create namespace `istio-testing` and enable injection.
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster2 create namespace istio-testing
kubectl <span class="nt">--context</span> cluster2 label namespace istio-testing istio-injection<span class="o">=</span>enabled</code></pre></figure>

- Apply httpbin deployment and service
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster2 apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: istio-testing
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: istio-testing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
EOF</span></code></pre></figure>

Cluster1 will communicate to this service via `httpbin.istio-testing.global` FQDN. But how? please read the official documentation. I am to lazy to explain in here, just contact me if you still confuse about the concept.

### Install sleep deployment and Istio ServiceEntry in Cluster1

ServiceEntry is a special object in Istio, this basically configure in `istiocoredns` to resolve `httpbin.istio-testing.global` into an unused ip `240.0.0.2` and force sidecar to forward the traffic into spesific endpoint (ingressgateway cluster2 with a special port 15443) and force mutual TLS.

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster1 create namespace istio-testing
kubectl <span class="nt">--context</span> cluster1 label namespace istio-testing istio-injection<span class="o">=</span>enabled</code></pre></figure>

- Apply httpbin deployment and service
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster1 apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: httpbin-cluster2-se
spec:
  hosts:
  - httpbin.istio-testing.global
  location: MESH_INTERNAL
  ports:
  - name: http
    number: 80
    protocol: http
  resolution: DNS
  addresses:
  - 240.0.0.2
  endpoints:
  - address: </span><span class="si">$(</span>minikube <span class="nt">--profile</span> cluster2 ip<span class="si">)</span><span class="sh">
    ports:
      http: </span><span class="si">$(</span>kubectl <span class="nt">--context</span> cluster2 get svc <span class="nt">-n</span> istio-system istio-ingressgateway <span class="nt">-o</span><span class="o">=</span><span class="nv">jsonpath</span><span class="o">=</span><span class="s1">'{.spec.ports[?(@.port==15443)].nodePort}'</span><span class="si">)</span><span class="sh">
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
  namespace: istio-testing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - name: sleep
        image: governmentpaas/curl-ssl
        command: ["/bin/sleep", "3650d"]
        imagePullPolicy: IfNotPresent
EOF</span></code></pre></figure>

## Testing
- Exec from sleep pod in Cluster1
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">kubectl <span class="nt">--context</span> cluster1 <span class="nt">-n</span> istio-testing <span class="nb">exec</span> <span class="nt">-it</span> <span class="si">$(</span>kubectl <span class="nt">--context</span> cluster1 <span class="nt">-n</span> istio-testing get pod | <span class="nb">grep</span> <span class="nt">-m</span> 1 <span class="nb">sleep</span> | <span class="nb">awk</span> <span class="s1">'{print $1}'</span><span class="si">)</span> <span class="nt">-c</span> <span class="nb">sleep </span>sh</code></pre></figure>
- Test nslookup
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">nslookup httpbin.istio-testing.global</code></pre></figure>
- Test curl
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">curl http://httpbin.istio-testing.global/headers</code></pre></figure>

Thanks

