I installed Linkerd and Nginx on an AKS cluster. You will need an Azure subscription and deploy a basic cluster.
Install the Linkerd CLI: see https://linkerd.io/2.11/getting-started/#step-1-install-the-cli. You can also install the cli with brew:
brew install linkerd
On Windows, use Chocolatey:
choco install linkerd
Use the following commands:
linkerd check --pre
linkerd install | kubectl apply -f -
linkerd check
The first command checks if your cluster meets the pre-requisites. The second command installs Linkerd in the linkerd
namespace. The third command checks if Linkerd is installed correctly.
linkerd check
command might time out. Check the pods manually with kubectl get pods -n linkerd
and run linkerd check
again.
The viz extension installs a metric stack on your cluster with Prometheus and Grafana. Use the following commands:
linkerd viz install | kubectl apply -f -
linkerd viz check
Apply the following YAML to your cluster:
apiVersion: v1
kind: Namespace
metadata:
name: linkerdapp
---
kind: Service
apiVersion: v1
metadata:
name: superapi
namespace: linkerdapp
spec:
selector:
app: superapi
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: superapi
spec:
replicas: 2
selector:
matchLabels:
app: superapi
template:
metadata:
labels:
app: superapi
annotations:
linkerd.io/inject: "enabled"
spec:
containers:
- name: superapi
image: ghcr.io/gbaeke/super:1.0.7
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "50m"
env:
- name: IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: WELCOME
value: Welcome from $(IP)
ports:
- containerPort: 8080
Save the above to a file and then run kubectl apply -f <file>
.
The super-api pods will be meshed because of the linkerd annotation in the deployment's pod template metadata:
annotations:
linkerd.io/inject: "enabled"
Start the dashboard with linkerd viz dashboard
. A browser will open and you will see the dashboard. If the browser does not open, use the displayed localhost link. You should see all namespaces. The following namespaces should be meshed:
- linkerdapp
- linkerd
- linkerd-viz
Save the following to a file called values.yaml
:
controller:
podAnnotations:
linkerd.io/inject: enabled
From the folder containing values.yaml, run the following command:
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--values values.yaml
The above command installs Nginx Ingress Controller in the ingress-nginx
namespace. It adds the linkerd.io/inject annotation to the ingress controller pods.
In the Linkerd UI, the namespace ingress-nginx
should be meshed.
Because the ingress contoller is meshed, the following happens:
- golden metrics are provided for the ingress controller (requests per second, etc.)
- traffic between the ingress controller pods and meshed application pods is encrypted (and mutually authenticated)
- HTTP traffic can be seen
- When applications return errors (e.g. 5xx HTTP status codes), this will be visible in the Linkerd UI for both the applications but also nginx ingress controller as it returns the error codes to the client.
Add the following ingress.yaml to the linkerdapp
namespace:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: superapi-ingress
labels:
name: superapi-ingress
namespace: linkerdapp
annotations:
# add below line of nginx is meshed
nginx.ingress.kubernetes.io/service-upstream: "true"
# nginx.ingress.kubernetes.io/affinity: "cookie"
# nginx.ingress.kubernetes.io/affinity-mode: "persistent"
spec:
ingressClassName: nginx
rules:
# update IP with your own IP used by Ingress Controller
- host: linkerd.REPLACE-WITH-YOUR-IP.nip.io
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: superapi
port:
number: 80
nip.io
is used to provide a custom domain name that resolves to the IP address of the Ingress Controller. You need to retrieve the IP address of the Ingress Controller from theingress-nginx
namespace. Usekubectl get svc -n ingress-nginx
and find the external IP. Replace the IP in the hostname above. This IP address is actually a front-end IP on thekubernetes
Azure public load balancer in the MC_ group of your AKS cluster.- the annotation
nginx.ingress.kubernetes.io/service-upstream: "true"
is used to tell Nginx Ingress Controller to route traffic to the service of the meshed application instead of directly to the pods. By default, Ingress Controllers merely query the endpoints of their target service to retrieve the IP addresses of the pods behind the service. They route traffic to the pods directly. By sending traffic to the service, Linkerd features such as load balancing and traffic splitting are enabled. - when you use the annotation above, you cannot have sticky sessions with cookie-based affinity
You can use hey
to send traffic to the super-api application via the ingress:
watch hey -n 1500 -c 50 http://linkerd.YOURIP.nip.io/flaky
Above, replace YOURIP with the IP address you used in the Ingress host. The /flaky
path is used to introduce errors. It returns a 5xx HTTP status code for about 10% of the requests.
From the Linkerd UI, check the traffic. You should see lots of red because you are not at 100% of the traffic.
You can use Azure Load Testing to hit the Ingress controller with traffic. Use the following file to configure the load test:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.1">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Azure Load Testing Quickstart" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">250</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">120</stringProp>
<stringProp name="ThreadGroup.delay">5</stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Homepage" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">linkerd.20.23.53.238.nip.io</stringProp>
<stringProp name="HTTPSampler.port">80</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/flaky</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
Above, modify HTTPSampler.domain
to the host you configured in the Ingress resource. Modify HTTPSampler.path
to the path you want to hit. You can use /
to not return errors or /flaky
to return errors.