DEV Community

Cover image for Usando Cilium no WSL
Cristiano Lemes
Cristiano Lemes

Posted on

Usando Cilium no WSL

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.

Ilustração ebpf
Guia Ilustrado do eBPF

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.

Documentação Cilium

"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

  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. Vamos fazer o replace das entrada LOCALVERVSION para generic
sed -i 's/microsoft-standard-WSL2/generic/' ./.config
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  • 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")
Enter fullscreen mode Exit fullscreen mode

Gist

  • Execute o script
python3 enable_conf.py
Enter fullscreen mode Exit fullscreen mode
  1. Agora só rodar o Make, pode deixar todas perguntas no padrão.
make -j $(nproc)
Enter fullscreen mode Exit fullscreen mode
  1. Finalizando a compilação, instale os moduloes.
sudo make modules_install
Enter fullscreen mode Exit fullscreen mode
  1. 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:.
  2. No Ubuntu crie o diretório.
mkdir /mnt/c/wslkernel
Enter fullscreen mode Exit fullscreen mode
  • Copie o novo kernel para a pasta vamos renomear para kernelcilium
cp arch/x86/boot/bzImage /mnt/c/wslkernel/kernelcilium
Enter fullscreen mode Exit fullscreen mode
  1. 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\\kernelnoble
Enter fullscreen mode Exit fullscreen mode
  1. Feche as janelas abertas com o wsl e derrube todas as distros.
wsl --shutdown
Enter fullscreen mode Exit fullscreen mode
  1. Abra o Ubuntu novamente e confirme se está usando o kernel novo.
uname -r
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. Vamos reiniciar o daemon e o serviço de modulos
sudo systemctl daemon-reload

sudo systemctl restart systemd-modules-load
Enter fullscreen mode Exit fullscreen mode
  1. 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
....

Enter fullscreen mode Exit fullscreen mode

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.

  1. 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"
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
  • 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"
}
Enter fullscreen mode Exit fullscreen mode
  • Agora é somente instalar com o scoop apontando para o arquivo json.
scoop install cilium.json
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode
$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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Verficando o status dos pods

k get pods -l k8s-app=cilium -n kube-**system**
k logs -l k8s-app=cilium -n kube-system
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Aplicando a política.

k apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/sw_l3_l4_policy.yaml
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
        ]
}
Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Aplicando a politica

k apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/sw_l3_l4_l7_policy.yaml
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Referências

WSL Kernel
Falco WSL
WSL Kernel Cilium

Top comments (0)