Criando um ambiente de teste do Cilium no WSL
eBPF a base do Cilium
O eBPF é umas das tecnologias mais faladas nos últimos tempos na comunidade de tecnologia,
isso graças sua capacidade de estender as funções do kernel sem precisar alterar código do kernel ou carregar modulos. Com eBPF você escreve programas em C ou Rust que são compilados em bytecode.
Afinal, o que é Cilium?
Cilium é um software de código aberto que aproveita das funcionalidades do eBPF para entregar ao kubernetes soluções para Ingress, gateways api, service mesh, segurança e observabilidade entre outras. Ele consegue atuar de forma transparente sem uso de container sidecar como Envoy.
"eBPF é uma tecnologia de kernel revolucionária que permite aos desenvolvedores escrever
código que pode ser carregado no kernel dinamicamente, mudando a maneira como o kernel
se comporta.
Isso permite uma nova geração de redes de alto desempenho, observabilidade e
ferramentas de segurança. E como você verá, se quiser instrumentar um aplicativo com essas ferramentas baseadas em eBPF, você não precisa modificar ou reconfigurar o aplicativo de qualquer forma, graças ao ponto de vista do eBPF dentro do kernel."
Liz Rice, no seu livro gratuíto Learning eBPF
A isovalent também contem vários labs gratuitos para aprender usar o Cilium e outras ferramentas da Isovalent, como o hubble, e ainda você ganha badges do Creddly 😍.
Compilando um novo Kernel para o WSL
Para conseguir carregar os módulos necessários vamos precisar compilar um kernel já com as funcionalidades necessárias ativas, o WSL padrão vem com o kernel 5.15, mas já que vamos precisar recompilar tudo, vamos colocar logo um mais novo, vamos baixar o kernel 6.8, que é a versão padrão do Ubuntu 24.04, também alguma features do Cilium somente estão disponíveis em versões mais novas do kernel como pode ver na tabela abaixo.
Meu ambiente
Sistema operacional: Windows 11 23H2
Distro WSL: Ubuntu 24.04 LTS
Versão do WSL: 2.1.5.0
Docker Desktop: 4.30
Gerenciador de pacotes: Scoop
Cilium Feature | Minimum Kernel Version |
---|---|
Bandwidth Manager | >= 5.1 |
Egress Gateway | >= 5.2 |
VXLAN Tunnel Endpoint (VTEP) Integration | >= 5.2 |
WireGuard Transparent Encryption | >= 5.6 |
Full support for Session Affinity | >= 5.7 |
BPF-based proxy redirection | >= 5.7 |
Socket-level LB bypass in pod netns | >= 5.7 |
L3 devices | >= 5.8 |
BPF-based host routing | >= 5.10 |
IPv6 BIG TCP support | >= 5.19 |
IPv4 BIG TCP support | >= 6.3 |
Abra o shell do Ubuntu WSL, no seu gerenciador de terminal, o meu é o Windows Terminal,
e siga os passos
- Instale as ferramentas necessárias para compilação
sudo apt update && sudo apt install build-essential flex bison libssl-dev libelf-dev bc python3 pahole
- Baixe o kernel no repositório do linux, baixando só a branch que vamos usar no casol linux-6.8.y.
## baixando do repositorio
git clone --depth 1 --branch linux-6.8.y https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
### Entre na pasta
cd linux
- Estando dentro da pasta, linux, vamos baixar o arquivo de configuração padrão do kernel do Wsl e salva-lo como .config.
wget https://raw.githubusercontent.com/microsoft/WSL2-Linux-Kernel/linux-msft-wsl-6.1.y/arch/x86/configs/config-wsl -O .config
- Vamos fazer o replace das entrada LOCALVERVSION para generic
sed -i 's/microsoft-standard-WSL2/generic/' ./.config
- Vamos ajustar o arquivo .config para atender a todos os requisitos do Cilium
- Vamos criar um arquivo chamado cilium_modules e colocar o conteudo abaixo dentro.
## linux/cilium_modules
## Base requirements
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_NET_CLS_BPF=y
CONFIG_BPF_JIT=y
CONFIG_NET_CLS_ACT=y
CONFIG_NET_SCH_INGRESS=y
CONFIG_CRYPTO_SHA1=y
CONFIG_CRYPTO_USER_API_HASH=y
CONFIG_CGROUPS=y
CONFIG_CGROUP_BPF=y
## Iptables-based Masquerading
CONFIG_NETFILTER_XT_SET=m
CONFIG_IP_SET=m
CONFIG_IP_SET_HASH_IP=m
## L7 and FQDN Policies
CONFIG_NETFILTER_XT_TARGET_TPROXY=m
CONFIG_NETFILTER_XT_TARGET_CT=m
CONFIG_NETFILTER_XT_MATCH_MARK=m
CONFIG_NETFILTER_XT_MATCH_SOCKET=m
## IPsec
CONFIG_XFRM=y
CONFIG_XFRM_OFFLOAD=y
CONFIG_XFRM_STATISTICS=y
CONFIG_XFRM_ALGO=m
CONFIG_XFRM_USER=m
CONFIG_INET_ESP=m
CONFIG_INET_IPCOMP=m
CONFIG_INET_XFRM_TUNNEL=m
CONFIG_INET_TUNNEL=m
CONFIG_INET6_ESP=m
CONFIG_INET6_IPCOMP=m
CONFIG_INET6_XFRM_TUNNEL=m
CONFIG_INET6_TUNNEL=m
CONFIG_INET_XFRM_MODE_TUNNEL=m
CONFIG_CRYPTO_AEAD=m
CONFIG_CRYPTO_AEAD2=m
CONFIG_CRYPTO_GCM=m
CONFIG_CRYPTO_SEQIV=m
CONFIG_CRYPTO_CBC=m
CONFIG_CRYPTO_HMAC=m
CONFIG_CRYPTO_SHA256=m
CONFIG_CRYPTO_AES=m
- Agora, vamos criar um script Python chamado enable_conf.py , para obter o conteúdo arquivo cilium_modules e ajustar o .config.
import re
# Lê o conteúdo do arquivo 'cilium_modules
config_replacements = {}
with open('cilium_modules', 'r', encoding='utf-8') as file1:
for line in file1:
line = line.strip()
# Ignora linhas vazias e comentários
if not line or line.startswith('##'):
continue
key, value = line.split('=')
config_replacements[key] = value
# Lê o conteúdo do arquivo '.config'
with open('.config', 'r') as file2:
file2_lines = file2.readlines()
# Mantém um conjunto para controle das chaves que foram atualizadas
updated_keys = set()
# Substitui linhas correspondentes em '.config'
with open('.config', 'w', encoding='utf-8') as file2:
for line in file2_lines:
# Verifica se a linha contém alguma chave de 'cilium_modules' usando regex
for key, value in config_replacements.items():
if re.search(r'\b' + re.escape(key) + r'\b', line):
# Se a linha estiver comentada, remove o símbolo de comentário e atualiza
if line.startswith('# ' + key):
line = f"{key}={value}\n"
# Atualiza o valor da linha
elif re.search(r'^\s*' + re.escape(key) + r'\b', line):
line = f"{key}={value}\n"
updated_keys.add(key)
break
file2.write(line)
# Adiciona as chaves que não foram encontradas ao '.config'
for key, value in config_replacements.items():
if key not in updated_keys:
file2.write(f"{key}={value}\n")
- Execute o script
python3 enable_conf.py
- Agora só rodar o Make, pode deixar todas perguntas no padrão.
make -j $(nproc)
- Finalizando a compilação, instale os moduloes.
sudo make modules_install
- Vamos criar uma pasta no Windows, para colocar o kernel novo, lembrando que todas as do WSL compartilham o mesmo kernel, então vamos colocar no drive C:.
- No Ubuntu crie o diretório.
mkdir /mnt/c/wslkernel
- Copie o novo kernel para a pasta vamos renomear para kernelcilium
cp arch/x86/boot/bzImage /mnt/c/wslkernel/kernelcilium
- Agora vamos alterar o .wslconfig para as distros subirem com o kernel novo, pode usar o seu editor de texto de preferencia, estando no windows, navegue até a pasta, $env:USERPROFILE e edite o .wslconfig, e adicione a configuração conforme abaixo.
[wsl2]
kernel = C:\\wslkernel\\kernelcilium
- Feche as janelas abertas com o wsl e derrube todas as distros.
wsl --shutdown
- Abra o Ubuntu novamente e confirme se está usando o kernel novo.
uname -r
- Vamos criar o arquivo de configuração para os modulos necessários carregarem na inicialização.
awk '(NR>1) { print $2 }' /usr/lib/modules/$(uname -r)/modules.alias | sudo tee /etc/modules-load.d/cilium.conf
- Vamos reiniciar o daemon e o serviço de modulos
sudo systemctl daemon-reload
sudo systemctl restart systemd-modules-load
- Checando se está tudo certo
$cris /kind ❱❱ sudo systemctl status systemd-modules-load
● systemd-modules-load.service - Load Kernel Modules
Loaded: loaded (/usr/lib/systemd/system/systemd-modules-load.service; static)
Active: active (exited) since Tue 2024-06-11 11:23:40 -03; 4h 59min ago
Docs: man:systemd-modules-load.service(8)
man:modules-load.d(5)
Process: 56 ExecStart=/usr/lib/systemd/systemd-modules-load (code=exited, status=0/SUCCESS)
Main PID: 56 (code=exited, status=0/SUCCESS)
Notice: journal has been rotated since unit was started, output may be incomplete.
$cris /kind ❱❱ lsmod
Module Size Used by
ipcomp6 12288 0
xfrm6_tunnel 12288 1 ipcomp6
tunnel6 12288 1 xfrm6_tunnel
esp6 24576 0
xfrm_user 53248 4
xfrm4_tunnel 12288 0
ipcomp 12288 0
xfrm_ipcomp 12288 2 ipcomp6,ipcomp
esp4 24576 0
xfrm_algo 16384 4 esp6,esp4,xfrm_ipcomp,xfrm_user
ip_set_hash_netportnet 49152 0
ip_set_hash_netnet 49152 0
ip_set_hash_netiface 45056 0
ip_set_hash_netport 45056 0
ip_set_hash_net 45056 0
ip_set_hash_mac 24576 0
ip_set_hash_ipportnet 45056 0
ip_set_hash_ipportip 40960 0
ip_set_hash_ipport 40960 0
ip_set_hash_ipmark 40960 0
ip_set_hash_ipmac 40960 0
....
Criando o Cluster kubernetes com Kind
Instalando o client Cilium, você pode fazer todas a instalação também usando Helm.
A partir de agora usaremos somente o powershell para criação dos recursos, primeiramente vamos criar um cluster usando o Kind.
- Crie o arquivo de configuração do kind, vamos desativar a rede padrão e o kubeproxy.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
# localhost.run proxy
- containerPort: 32042
hostPort: 32042
# Hubble relay
- containerPort: 31234
hostPort: 31234
# Hubble UI
- containerPort: 31235
hostPort: 31235
- role: worker
- role: worker
networking:
disableDefaultCNI: true
kubeProxyMode: "none"
- Agora vamos instalar o Cilium no cluster para isso vamos usar o cliente do cilium, a instalação também pode ser feita via helm.
- Baixe a ultima release do Cilium para sua plataforma, e descompacte em uma pasta de sua preferencia, lembrando que para executar em qualquer prompt precisa colocar o local do executavel na varivel de ambiente PATH.
## Baixando
aria2c https://github.com/cilium/cilium-cli/releases/download/v0.16.10/cilium-windows-amd64.zip
## descompactando
unzip.exe .\cilium-windows-amd64.zip
- A opção que prefiro é usar o Scoop, o Cilium não está em nenhum bucket official, então vamos precisar criar uma instalação customizada.
- Crie uma arquivo chamado cilium.json e coloque o conteúdo abaixo.
{
"bin": "cilium.exe",
"version": "v0.16.10",
"url": https://github.com/cilium/ciliumcli/releases/download/v0.16.10/cilium-windows-amd64.zip"
}
- Agora é somente instalar com o scoop apontando para o arquivo json.
scoop install cilium.json
- Agora é só executar o comando do cilium para instalar ele no cluster ele vai achar seu cluster pelo contexto atual do .kube/config, pode confirmar usando o comando
kubectl config get-contexts
,
cilium install
$cris /kind ❱❱ cilium install
🔮 Auto-detected Kubernetes kind: kind
✨ Running "kind" validation checks
✅ Detected kind version "0.23.0"
ℹ️ Using Cilium version 1.15.5
🔮 Auto-detected cluster name: kind-kind
ℹ️ Detecting real Kubernetes API server addr and port on Kind
🔮 Auto-detected kube-proxy has not been installed
ℹ️ Cilium will fully replace all functionalities of kube-proxy
Depois de alguns minutos o Cilium está pronto, podemos verificar os status do cilium com o cli.
cris /kind ❱❱ cilium status
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: disabled (using embedded mode)
\__/¯¯\__/ Hubble Relay: disabled
\__/ ClusterMesh: disabled
Deployment cilium-operator Desired: 1, Ready: 1/1, Available: 1/1
DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
Containers: cilium Running: 3
cilium-operator Running: 1
Cluster Pods: 3/3 managed by Cilium
Helm chart version:
Image versions cilium quay.io/cilium/cilium:v1.15.5@sha256:4ce1666a73815101ec9a4d360af6c5b7f1193ab00d89b7124f8505dee147ca40: 3
cilium-operator quay.io/cilium/operator-generic:v1.15.5@sha256:f5d3d19754074ca052be6aac5d1ffb1de1eb5f2d947222b5f10f6d97ad4383e8: 1
Caso exiba algum erro, você pode dar uma olhada no status do daemont set, e conferir os logs dos pods.
$cris /kind ❱❱ k get daemonsets -n kube-system
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 6m47s daemonset-controller Created pod: cilium-c74rc
Normal SuccessfulCreate 6m47s daemonset-controller Created pod: cilium-b7rrn
Normal SuccessfulCreate 6m47s daemonset-controller Created pod: cilium-wmxlx
Verficando o status dos pods
k get pods -l k8s-app=cilium -n kube-**system**
k logs -l k8s-app=cilium -n kube-system
Se der algum erro em modulo, pode ter faltado algum passo da etapa build do kernel, pode analisar novamente. Pode pegar o nome do modulo que deu erro e tentar carrega-lo, usando modprobe.
Ex:
sudo modprobe xt_TPROXY
Se não der erro e aparecer no lsmod, provalmente só faltar por ele no boot do linux, como foi feito na parte 12 da compilação do kernel.
Testando o ambiente.
Para o teste vamos usar o app Star Wars Demo do lab Getting Started with Cilium da Isovalent.
Neste lab, fazemos o deploy de um microserviço simples, temos um deployment chamado DeathStar, que vai receber as requisições POST dos pods xwing e tiefigher, vamos usar o Cilium para controlar a comunicação entre os pods, baseando-se nos labels configurados.
Os Labels:
- Death Star:
org=empire, class=deathstar
- Imperio TIE fighter:
org=empire, class=tiefighter
- Rebel X-Wing:
org=alliance, class=xwing
Vamos usar criar o app no cluster usando o yaml http-sw-app.yaml
:.
k apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/http-sw-app.yaml
Checando a criação dos recursos.
$cris /kind ❱❱ k get pod,deploy,svc
NAME READY STATUS RESTARTS AGE
pod/deathstar-689f66b57d-9c92f 1/1 Running 0 29m
pod/deathstar-689f66b57d-b4ps7 1/1 Running 0 29m
pod/tiefighter 1/1 Running 0 29m
pod/xwing 1/1 Running 0 29m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deathstar 2/2 2 2 29m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/deathstar ClusterIP 10.96.120.87 <none> 80/TCP 29m
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 130m
O manifesto cria também um serviço para gerenciar a comunicação com a DeathStar, vamos usar o exec para simular que estamos execuntando o comando a partir dos pods xwing.
k exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
k exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
No momento sem politicas ativas, as duas naves podem pousar e a api responde "Ship landed"
Vamos criar uma politica usando o cilium, abaixo o manifesto da politica, vamos fazer um bloqueio simples de porta.
Essa politica abaixo atua nas camadas de rede 3 e 4, em suma podemos controlar IP e Porta.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "L3-L4 policy to restrict deathstar access to empire ships only"
# definindo o pod que vai receber a requisição (No caso a DeathStar)
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
# definindo a origem da conexão, somente permitindo o pod com o label org = empire de acessar na porta 80.
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
Aplicando a política.
k apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/sw_l3_l4_policy.yaml
Testando as politicas
k exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
k exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Agora só o tiefighter recebe o retorno da API, o xwing não consegue conectar, pode dar um CTRL+C para sair.
Agora queremos que o tiefighter somente use a área de pouso, nossa api tem outros endpoints, mas somente queremos que utilize o /request-landing, para isso precisamos criar um regra de HTTP, como nas regras de cama 3 e 4 só trabalhamos com ip e porta, vamos precisar criar uma regra de camada 7, para controlar o tráfego http:
Endpoints:
$cris /kind ❱❱ k exec tiefighter -- curl -s -get deathstar.default.svc.cluster.local/v1
{
"name": "Death Star",
"hostname": "deathstar-689f66b57d-9c92f",
"model": "DS-1 Orbital Battle Station",
"manufacturer": "Imperial Department of Military Research, Sienar Fleet Systems",
"cost_in_credits": "1000000000000",
"length": "120000",
"crew": "342953",
"passengers": "843342",
"cargo_capacity": "1000000000000",
"hyperdrive_rating": "4.0",
"starship_class": "Deep Space Mobile Battlestation",
"api": [
"GET /v1",
"GET /v1/healthz",
"POST /v1/request-landing",
"PUT /v1/cargobay",
"GET /v1/hyper-matter-reactor/status",
"PUT /v1/exhaust-port"
]
}
Para ajustar o yaml para controlar o trafego http, simplesmente adicionamos o campo rules no manifesto, adicionando mais refino da politica.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "L7 policy to restrict access to specific HTTP call"
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/request-landing"
Antes de ter politicas, conseguimos facilmente destruir a DeathStar.
$cris /kind ❱❱ k exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Panic: deathstar exploded
goroutine 1 [running]:
main.HandleGarbage(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
/code/src/github.com/empire/deathstar/
temp/main.go:9 +0x64
main.main()
/code/src/github.com/empire/deathstar/
temp/main.go:5 +0x85
Aplicando a politica
k apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/sw_l3_l4_l7_policy.yaml
Testando.
k exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
k exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Continuamos conseguindo pousar, mas a porta de exaustão está protegida contra Tiefighters.
$cris /kind ❱❱ k exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
$cris /kind ❱❱ k exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Access denied
Top comments (0)