Linkerd Service Profiles Configuration: Ultra-Lightweight Service Mesh
Linkerd stands out as the ultralight, security-first service mesh designed specifically for Kubernetes. With its focus on simplicity, performance, and reliability, Linkerd provides production-grade service mesh capabilities with minimal resource overhead. This comprehensive guide explores advanced Linkerd configurations, service profiles, traffic management, and enterprise deployment patterns.
Linkerd Architecture and Design Philosophy
Linkerd’s architecture emphasizes simplicity and performance through its Rust-based micro-proxy design, delivering a service mesh that consumes significantly fewer resources than alternatives while providing comprehensive observability, reliability, and security features.
Production-Grade Linkerd Installation
#!/bin/bash
# Production Linkerd installation with HA configuration
set -euo pipefail
LINKERD_VERSION="stable-2.14.10"
echo "Installing Linkerd ${LINKERD_VERSION}..."
# Install Linkerd CLI
curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh
export PATH=$PATH:$HOME/.linkerd2/bin
# Verify cluster compatibility
linkerd check --pre
# Generate certificates for production (using cert-manager)
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: linkerd-trust-anchor
namespace: linkerd
spec:
ca:
secretName: linkerd-trust-anchor
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: linkerd-trust-anchor
namespace: linkerd
spec:
secretName: linkerd-trust-anchor
duration: 87600h # 10 years
renewBefore: 8760h # 1 year
isCA: true
commonName: root.linkerd.cluster.local
dnsNames:
- root.linkerd.cluster.local
issuerRef:
name: linkerd-trust-anchor
kind: Issuer
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: linkerd-identity-issuer
namespace: linkerd
spec:
ca:
secretName: linkerd-identity-issuer
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: linkerd-identity-issuer
namespace: linkerd
spec:
secretName: linkerd-identity-issuer
duration: 48h
renewBefore: 24h
isCA: true
commonName: identity.linkerd.cluster.local
dnsNames:
- identity.linkerd.cluster.local
issuerRef:
name: linkerd-trust-anchor
kind: Issuer
usages:
- cert sign
- crl sign
- server auth
- client auth
EOF
# Wait for certificates
kubectl wait --for=condition=Ready \
certificate/linkerd-trust-anchor \
certificate/linkerd-identity-issuer \
-n linkerd \
--timeout=300s
# Install Linkerd control plane with HA
linkerd install \
--identity-external-issuer \
--identity-trust-anchors-file <(kubectl get secret linkerd-trust-anchor -n linkerd -o jsonpath='{.data.ca\.crt}' | base64 -d) \
--identity-issuer-certificate-file <(kubectl get secret linkerd-identity-issuer -n linkerd -o jsonpath='{.data.tls\.crt}' | base64 -d) \
--identity-issuer-key-file <(kubectl get secret linkerd-identity-issuer -n linkerd -o jsonpath='{.data.tls\.key}' | base64 -d) \
--ha \
--set controllerReplicas=3 \
--set proxyInjector.replicas=3 \
--set spValidator.replicas=3 \
--set tap.replicas=3 \
--set destinationReplicas=3 \
--set identityReplicas=3 \
--set proxyInjector.resources.cpu.request=100m \
--set proxyInjector.resources.memory.request=128Mi \
--set proxyInjector.resources.cpu.limit=1000m \
--set proxyInjector.resources.memory.limit=1Gi \
--set proxy.resources.cpu.request=100m \
--set proxy.resources.memory.request=20Mi \
--set proxy.resources.cpu.limit=1000m \
--set proxy.resources.memory.limit=250Mi \
--set proxy.cores=2 \
--set proxy.logLevel=info \
--set proxy.logFormat=json \
--set proxy.await=true \
--set clusterNetworks="10.244.0.0/16\,10.96.0.0/12" \
| kubectl apply -f -
# Wait for Linkerd to be ready
echo "Waiting for Linkerd to be ready..."
linkerd check
# Install Linkerd Viz extension for observability
linkerd viz install \
--ha \
--set dashboard.replicas=2 \
--set prometheus.replicas=2 \
--set tap.replicas=2 \
| kubectl apply -f -
# Install Linkerd Multicluster extension
linkerd multicluster install | kubectl apply -f -
# Install Linkerd Jaeger extension
linkerd jaeger install | kubectl apply -f -
echo "Linkerd installation completed successfully!"
linkerd check
Advanced Linkerd Configuration
# Linkerd proxy configuration via ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: linkerd-config
namespace: linkerd
data:
# Global proxy configuration
global: |
{
"linkerdNamespace": "linkerd",
"cniEnabled": false,
"version": "stable-2.14.10",
"identityContext": {
"trustDomain": "cluster.local",
"trustAnchorsPem": "...",
"issuanceLifetime": "86400s",
"clockSkewAllowance": "20s"
},
"autoInjectContext": null,
"omitWebhookSideEffects": false,
"clusterDomain": "cluster.local",
"clusterNetworks": "10.244.0.0/16,10.96.0.0/12",
"podMonitor": {
"enabled": true,
"controller": {
"enabled": true
},
"proxy": {
"enabled": true
},
"serviceMirror": {
"enabled": true
}
}
}
# Proxy configuration
proxy: |
{
"proxyImage": {
"name": "cr.l5d.io/linkerd/proxy",
"pullPolicy": "IfNotPresent",
"version": "stable-2.14.10"
},
"proxyInitImage": {
"name": "cr.l5d.io/linkerd/proxy-init",
"pullPolicy": "IfNotPresent",
"version": "v2.2.1"
},
"controlPort": {
"port": 4190
},
"ignoreInboundPorts": "25,587,3306,5432,11211",
"ignoreOutboundPorts": "25,587",
"inboundPort": {
"port": 4143
},
"adminPort": {
"port": 4191
},
"outboundPort": {
"port": 4140
},
"resource": {
"requestCpu": "100m",
"requestMemory": "20Mi",
"limitCpu": "1000m",
"limitMemory": "250Mi"
},
"proxyUid": 2102,
"logLevel": "info",
"logFormat": "json",
"disableExternalProfiles": false,
"proxy": {
"await": true,
"enableExternalProfiles": true,
"cores": 2
},
"enableGateway": false,
"nativeSidecar": false,
"workloadKind": ""
}
---
# Linkerd control plane deployment with HA
apiVersion: apps/v1
kind: Deployment
metadata:
name: linkerd-destination
namespace: linkerd
labels:
app.kubernetes.io/name: destination
app.kubernetes.io/part-of: Linkerd
app.kubernetes.io/version: stable-2.14.10
linkerd.io/control-plane-component: destination
spec:
replicas: 3
selector:
matchLabels:
linkerd.io/control-plane-component: destination
template:
metadata:
labels:
linkerd.io/control-plane-component: destination
annotations:
linkerd.io/inject: disabled
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
linkerd.io/control-plane-component: destination
topologyKey: kubernetes.io/hostname
containers:
- name: destination
image: cr.l5d.io/linkerd/controller:stable-2.14.10
args:
- destination
- -addr=:8086
- -controller-namespace=linkerd
- -enable-h2-upgrade=true
- -log-level=info
- -log-format=json
- -enable-endpoint-slices
- -cluster-domain=cluster.local
ports:
- containerPort: 8086
name: grpc
- containerPort: 9996
name: admin-http
livenessProbe:
httpGet:
path: /ping
port: 9996
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 9996
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 1Gi
securityContext:
runAsUser: 2103
readOnlyRootFilesystem: true
serviceAccountName: linkerd-destination
Service Profiles for Per-Route Metrics
Service Profiles enable Linkerd to provide per-route metrics, retries, and timeouts at the HTTP/gRPC level:
# Service Profile for REST API
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: backend-api.production.svc.cluster.local
namespace: production
spec:
# Define routes for detailed metrics
routes:
# User endpoints
- name: GET /api/users
condition:
method: GET
pathRegex: /api/users
timeout: 1000ms
retryBudget:
minRetriesPerSecond: 10
retryRatio: 0.2
ttl: 10s
isRetryable: true
responseClasses:
- condition:
status:
min: 500
max: 599
isFailure: true
- name: POST /api/users
condition:
method: POST
pathRegex: /api/users
timeout: 5000ms
isRetryable: false
- name: GET /api/users/{id}
condition:
method: GET
pathRegex: /api/users/[^/]+
timeout: 1000ms
retryBudget:
minRetriesPerSecond: 10
retryRatio: 0.2
ttl: 10s
isRetryable: true
- name: PUT /api/users/{id}
condition:
method: PUT
pathRegex: /api/users/[^/]+
timeout: 3000ms
isRetryable: false
- name: DELETE /api/users/{id}
condition:
method: DELETE
pathRegex: /api/users/[^/]+
timeout: 2000ms
isRetryable: false
# Order endpoints
- name: GET /api/orders
condition:
method: GET
pathRegex: /api/orders
timeout: 2000ms
isRetryable: true
- name: POST /api/orders
condition:
method: POST
pathRegex: /api/orders
timeout: 10000ms
isRetryable: false
responseClasses:
- condition:
status:
min: 500
max: 599
isFailure: true
- condition:
status:
min: 400
max: 499
isFailure: false
# Payment endpoints (non-retryable)
- name: POST /api/payments
condition:
method: POST
pathRegex: /api/payments
timeout: 30000ms
isRetryable: false
# Destination overrides for external services
dstOverrides:
- authority: external-api.example.com
weight: 100
---
# Service Profile for gRPC service
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: user-service.production.svc.cluster.local
namespace: production
spec:
routes:
# GetUser RPC
- name: /user.v1.UserService/GetUser
condition:
method: POST
pathRegex: /user\.v1\.UserService/GetUser
timeout: 1000ms
retryBudget:
minRetriesPerSecond: 10
retryRatio: 0.2
ttl: 10s
isRetryable: true
# CreateUser RPC
- name: /user.v1.UserService/CreateUser
condition:
method: POST
pathRegex: /user\.v1\.UserService/CreateUser
timeout: 5000ms
isRetryable: false
# UpdateUser RPC
- name: /user.v1.UserService/UpdateUser
condition:
method: POST
pathRegex: /user\.v1\.UserService/UpdateUser
timeout: 3000ms
isRetryable: false
# ListUsers RPC
- name: /user.v1.UserService/ListUsers
condition:
method: POST
pathRegex: /user\.v1\.UserService/ListUsers
timeout: 5000ms
isRetryable: true
---
# Service Profile generation script
apiVersion: batch/v1
kind: Job
metadata:
name: generate-service-profiles
namespace: production
spec:
template:
metadata:
annotations:
linkerd.io/inject: disabled
spec:
serviceAccountName: service-profile-generator
containers:
- name: generator
image: cr.l5d.io/linkerd/cli:stable-2.14.10
command:
- /bin/sh
- -c
- |
# Generate service profile from OpenAPI spec
linkerd profile --open-api /specs/backend-api.yaml \
backend-api.production.svc.cluster.local \
| kubectl apply -f -
# Generate service profile from Protobuf
linkerd profile --proto /specs/user-service.proto \
user-service.production.svc.cluster.local \
| kubectl apply -f -
volumeMounts:
- name: specs
mountPath: /specs
volumes:
- name: specs
configMap:
name: api-specifications
restartPolicy: OnFailure
Traffic Splitting and Canary Deployments
# TrafficSplit for canary deployments
apiVersion: split.smi-spec.io/v1alpha2
kind: TrafficSplit
metadata:
name: backend-api-canary
namespace: production
spec:
# The root service that clients use
service: backend-api
# Backends with traffic weights
backends:
- service: backend-api-stable
weight: 900 # 90% traffic
- service: backend-api-canary
weight: 100 # 10% traffic
---
# Stable version service
apiVersion: v1
kind: Service
metadata:
name: backend-api-stable
namespace: production
spec:
ports:
- port: 8080
name: http
selector:
app: backend-api
version: stable
---
# Canary version service
apiVersion: v1
kind: Service
metadata:
name: backend-api-canary
namespace: production
spec:
ports:
- port: 8080
name: http
selector:
app: backend-api
version: canary
---
# Stable deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api-stable
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: backend-api
version: stable
template:
metadata:
labels:
app: backend-api
version: stable
annotations:
linkerd.io/inject: enabled
config.linkerd.io/proxy-cpu-request: "100m"
config.linkerd.io/proxy-memory-request: "20Mi"
config.linkerd.io/proxy-cpu-limit: "1000m"
config.linkerd.io/proxy-memory-limit: "250Mi"
spec:
containers:
- name: api
image: backend-api:v1.0.0
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
---
# Canary deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api-canary
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: backend-api
version: canary
template:
metadata:
labels:
app: backend-api
version: canary
annotations:
linkerd.io/inject: enabled
config.linkerd.io/proxy-cpu-request: "100m"
config.linkerd.io/proxy-memory-request: "20Mi"
spec:
containers:
- name: api
image: backend-api:v2.0.0
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
---
# Progressive traffic shift automation
apiVersion: batch/v1
kind: CronJob
metadata:
name: canary-traffic-shifter
namespace: production
spec:
schedule: "*/10 * * * *" # Every 10 minutes
jobTemplate:
spec:
template:
metadata:
annotations:
linkerd.io/inject: disabled
spec:
serviceAccountName: canary-controller
containers:
- name: shifter
image: bitnami/kubectl:latest
command:
- /bin/bash
- -c
- |
#!/bin/bash
set -e
# Get current canary weight
CURRENT_WEIGHT=$(kubectl get trafficsplit backend-api-canary \
-n production -o jsonpath='{.spec.backends[1].weight}')
# Get canary error rate
ERROR_RATE=$(linkerd viz stat deploy/backend-api-canary \
-n production --from deploy/backend-api-stable \
-o json | jq -r '.[0].stats.successRate')
# Check if error rate is acceptable (>99%)
if (( $(echo "$ERROR_RATE > 0.99" | bc -l) )); then
# Increase canary traffic by 10%
NEW_WEIGHT=$((CURRENT_WEIGHT + 100))
if [ $NEW_WEIGHT -ge 1000 ]; then
NEW_WEIGHT=1000
echo "Canary fully promoted"
fi
# Update traffic split
kubectl patch trafficsplit backend-api-canary \
-n production --type=json \
-p="[
{\"op\": \"replace\", \"path\": \"/spec/backends/0/weight\", \"value\": $((1000 - NEW_WEIGHT))},
{\"op\": \"replace\", \"path\": \"/spec/backends/1/weight\", \"value\": $NEW_WEIGHT}
]"
else
echo "Canary error rate too high, rolling back"
# Rollback to stable
kubectl patch trafficsplit backend-api-canary \
-n production --type=json \
-p="[
{\"op\": \"replace\", \"path\": \"/spec/backends/0/weight\", \"value\": 1000},
{\"op\": \"replace\", \"path\": \"/spec/backends/1/weight\", \"value\": 0}
]"
fi
restartPolicy: OnFailure
Authorization Policies
# Server resource defining authorization policy
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
name: backend-api-server
namespace: production
spec:
podSelector:
matchLabels:
app: backend-api
port: http
proxyProtocol: HTTP/2
---
# ServerAuthorization for frontend access
apiVersion: policy.linkerd.io/v1beta1
kind: ServerAuthorization
metadata:
name: frontend-to-backend
namespace: production
spec:
server:
name: backend-api-server
client:
meshTLS:
serviceAccounts:
- name: frontend
namespace: production
---
# ServerAuthorization for authenticated services
apiVersion: policy.linkerd.io/v1beta1
kind: ServerAuthorization
metadata:
name: authenticated-services
namespace: production
spec:
server:
name: backend-api-server
client:
meshTLS:
identities:
- "*.production.serviceaccount.identity.linkerd.cluster.local"
---
# HTTPRoute for route-level authorization
apiVersion: policy.linkerd.io/v1alpha1
kind: HTTPRoute
metadata:
name: backend-api-routes
namespace: production
spec:
parentRefs:
- name: backend-api-server
kind: Server
group: policy.linkerd.io
rules:
# Public read-only endpoints
- matches:
- path:
type: PathPrefix
value: /api/public
- method: GET
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: x-route-type
value: public
# Authenticated write endpoints
- matches:
- path:
type: PathPrefix
value: /api/users
- method: POST
- matches:
- path:
type: PathPrefix
value: /api/users
- method: PUT
- matches:
- path:
type: PathPrefix
value: /api/users
- method: DELETE
---
# Authorization for specific routes
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: api-write-authz
namespace: production
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: backend-api-routes
requiredAuthenticationRefs:
- name: authenticated-services
kind: ServerAuthorization
Monitoring and Observability
# Prometheus ServiceMonitor for Linkerd
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: linkerd-controller
namespace: linkerd
spec:
selector:
matchLabels:
linkerd.io/control-plane-component: controller
endpoints:
- port: admin-http
interval: 30s
path: /metrics
---
# PodMonitor for application proxies
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: linkerd-proxy
namespace: production
spec:
selector:
matchExpressions:
- key: linkerd.io/control-plane-ns
operator: Exists
podMetricsEndpoints:
- port: linkerd-admin
interval: 30s
path: /metrics
---
# Grafana dashboard ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: linkerd-dashboards
namespace: monitoring
data:
linkerd-service-performance.json: |
{
"dashboard": {
"title": "Linkerd Service Performance",
"panels": [
{
"title": "Request Rate",
"targets": [{
"expr": "sum(rate(request_total[1m])) by (dst_service)"
}]
},
{
"title": "Success Rate",
"targets": [{
"expr": "sum(rate(request_total{classification='success'}[1m])) / sum(rate(request_total[1m]))"
}]
},
{
"title": "P99 Latency",
"targets": [{
"expr": "histogram_quantile(0.99, sum(rate(response_latency_ms_bucket[1m])) by (le, dst_service))"
}]
}
]
}
}
Advanced Troubleshooting
#!/bin/bash
# Linkerd troubleshooting toolkit
# Check Linkerd health
check_health() {
echo "=== Checking Linkerd Health ==="
linkerd check
linkerd viz check
}
# Get service metrics
get_metrics() {
local namespace=$1
local service=$2
echo "=== Metrics for $namespace/$service ==="
linkerd viz stat deploy/$service -n $namespace
linkerd viz routes deploy/$service -n $namespace
linkerd viz tap deploy/$service -n $namespace
}
# Debug service profile
debug_profile() {
local namespace=$1
local service=$2
echo "=== Service Profile for $service ==="
kubectl get serviceprofile \
${service}.${namespace}.svc.cluster.local \
-n $namespace -o yaml
}
# Check authorization policies
check_authz() {
local namespace=$1
echo "=== Authorization Policies in $namespace ==="
kubectl get server,serverauthorization,httproute,authorizationpolicy \
-n $namespace -o wide
}
# Live traffic tap
tap_traffic() {
local namespace=$1
local resource=$2
echo "=== Tapping traffic for $namespace/$resource ==="
linkerd viz tap $resource -n $namespace --path /api/
}
# Export diagnostics
export_diagnostics() {
local output="linkerd-diagnostics-$(date +%Y%m%d-%H%M%S)"
mkdir -p $output
echo "Collecting Linkerd diagnostics..."
linkerd check > $output/linkerd-check.txt
linkerd viz check > $output/linkerd-viz-check.txt
kubectl get all -n linkerd -o yaml > $output/linkerd-resources.yaml
kubectl get serviceprofile --all-namespaces -o yaml > $output/service-profiles.yaml
kubectl get server,serverauthorization --all-namespaces -o yaml > $output/authz-policies.yaml
linkerd viz stat deploy --all-namespaces > $output/all-deployments-stats.txt
tar czf $output.tar.gz $output
echo "Diagnostics saved to $output.tar.gz"
}
case "${1:-help}" in
health) check_health ;;
metrics) get_metrics "$2" "$3" ;;
profile) debug_profile "$2" "$3" ;;
authz) check_authz "$2" ;;
tap) tap_traffic "$2" "$3" ;;
diagnostics) export_diagnostics ;;
*)
echo "Usage: $0 {health|metrics|profile|authz|tap|diagnostics}"
exit 1
;;
esac
Multi-Cluster Configuration
#!/bin/bash
# Configure Linkerd multi-cluster
# Link clusters
link_clusters() {
local source_cluster=$1
local target_cluster=$2
echo "Linking $source_cluster to $target_cluster..."
# On target cluster
kubectl --context=$target_cluster create ns linkerd-multicluster
linkerd --context=$target_cluster multicluster link \
--cluster-name $target_cluster \
--gateway=true \
--gateway-addresses=10.0.0.1 | \
kubectl --context=$source_cluster apply -f -
echo "Clusters linked successfully"
}
# Export services
export_service() {
local namespace=$1
local service=$2
kubectl label svc/$service -n $namespace \
mirror.linkerd.io/exported=true
}
link_clusters "cluster-1" "cluster-2"
export_service "production" "backend-api"
Conclusion
Linkerd delivers ultra-lightweight service mesh capabilities with minimal resource overhead while providing comprehensive observability, security through mTLS, and sophisticated traffic management. Through service profiles, traffic splitting, and authorization policies, Linkerd enables production-grade microservices deployments with exceptional simplicity and reliability.