# Auto-Instrumentação de uma aplicação Python usando OTLP Operator

## Introdução:

Anteriormente, realizamos a instrumentação manual da nossa aplicação. Neste artigo, vamos explorar a **auto-instrumentação utilizando o OpenTelemetry Operator**, uma funcionalidade que simplifica significativamente o processo de instrumentação em ambientes Kubernetes.

Todo o código desta etapa está disponível em:

* **Repositório:** [https://github.com/joaochiroli/otel-instrumentacao-python](https://github.com/joaochiroli/otel-instrumentacao-python)
    
* **Branch:** `kubernetes-operator`
    
* **Diretório:** `/kubernetes`
    

## Etapas de Implementação:

Neste artigo, você aprenderá a configurar a auto-instrumentação seguindo estas etapas:

1. **Preparação da aplicação**
    
    * Alteração do Dockerfile
        
    * Alteração do requirements.txt
        
2. **Instalação de dependências**
    
    * Instalação do Cert Manager
        
    * Instalação do OpenTelemetry Operator
        
3. **Configuração do ambiente Kubernetes**
    
    * Criação do namespace
        
    * Criação e instalação do banco de dados (PostgreSQL)
        
4. **Configuração da observabilidade**
    
    * Criação e instalação do Jaeger
        
    * Criação e instalação do OpenTelemetry Collector
        
    * Criação e instalação do Instrumentation
        
5. **Deploy da aplicação**
    
    * Criação e instalação dos manifestos da aplicação Flask
        

## **Preparação da aplicação**

Alteramos o arquivo requirements.txt para instalarmos apenas as dependências usadas pela aplicação, retiramos as dependências do OTLP.

```yaml
flask 
Flask-SQLAlchemy
psycopg2-binary
flask_sqlalchemy
```

Fizemos o mesmo para o Dockerfile:

```dockerfile
FROM python:3.9-slim

# Defina o diretório de trabalho
WORKDIR /app

# Copie os arquivos necessários para o diretório de trabalho
COPY . /app

# Instale as dependências
RUN pip install -r requirements.txt

# Exponha a porta que a aplicação vai rodar
EXPOSE 5000

# Comando para rodar a aplicação
CMD ["python", "app.py"]
```

## **Instalação de dependências**

Para o o OTLP Operator funcionar precisa ter instalado o Cert Manager:

```yaml
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml
```

Use o comando **kubectl get pods -A** para verificar se seus pods estão executando corretamente

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759587753089/444ebfce-be97-4133-8c44-2b7cda4faafb.png align="center")

Vamos instalar o operator com o seguinte comando:

```yaml
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
```

Use o comando **kubectl get pods -A** para verificar se seus pods estão executando corretamente

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759587816070/f9a27a3c-9ee8-41f0-a636-25dba19a5f0e.png align="center")

## Configuração do ambiente Kubernetes

Criamos um namespace pro nosso projeto.

Para fazer o deploy do namespace execute **kubectl apply -f namespace.yaml:**

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: simple-app
  labels:
    name: simple-app
```

Para fazer o deploy do configmap execute **kubectl apply -f configmap-app.yaml:**

```yaml
# configmap-app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: flask-app-config
  namespace: simple-app
data:
  DATABASE_URL: "postgresql://postgres:postgres@postgres-db:5432/simple_app"
  FLASK_ENV: "development"
  FLASK_DEBUG: "false"
```

Para fazer o deploy do secret execute **kubectl apply -f secret-postgres.yaml:**

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
  namespace: simple-app
type: Opaque
data:
  postgres-password: cG9zdGdyZXM=  # postgres em base64
  postgres-user: cG9zdGdyZXM=     # postgres em base64
  postgres-db: c2ltcGxlX2FwcA==   # simple_app em base64
```

Para fazer o deploy do Banco de Dados PostgreSQL execute **kubectl apply -f postgres.yaml:**

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: simple-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:latest
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: postgres-db
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: postgres-user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: postgres-password
        - name: POSTGRES_HOST_AUTH_METHOD
          value: "md5"
        # - name: PGDATA
        #   value: /var/lib/postgresql/data/pgdata
        # volumeMounts:
        # - name: postgres-storage
        #   mountPath: /var/lib/postgresql/data
        #   subPath: pgdata
        livenessProbe:
          exec:
            command:
            - pg_isready
            - -U
            - postgres
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - pg_isready
            - -U
            - postgres
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: simple-app
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: simple-app
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```

Você pode verificar se os pods estão executando usando o comando **kubectl get pods -n simple-app**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759588315549/d9eb72c1-7e03-4b20-ab43-9c3e13acc440.png align="center")

E vendo os logs do pod pra saber se está sendo executado como deveria. **kubectl logs &lt;nome do pod&gt; -n simple-app**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759588397762/1a8c6039-8c75-45e2-af5d-1cc6405ea111.png align="center")

## **Configuração da observabilidade**

Podemos seguir com os próximos passos. O primeiro deles é subir nosso backend que no caso é o Jaeger. Para fazer o deploy execute **kubectl apply -f jaeger.yaml:**

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jaeger
  namespace: simple-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jaeger
  template:
    metadata:
      labels:
        app: jaeger
    spec:
      containers:
      - name: jaeger
        image: jaegertracing/all-in-one:latest
        ports:
        - containerPort: 5775
          protocol: UDP
        - containerPort: 6831
          protocol: UDP
        - containerPort: 6832
          protocol: UDP
        - containerPort: 5778
          protocol: TCP
        - containerPort: 16686
          protocol: TCP
        - containerPort: 4317
          protocol: TCP
        - containerPort: 4318
          protocol: TCP
        - containerPort: 14250
          protocol: TCP
        - containerPort: 14268
          protocol: TCP
        - containerPort: 14269
          protocol: TCP
        - containerPort: 9411
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: jaeger-query
  namespace: simple-app
spec:
  ports:
  - port: 16686
    targetPort: 16686
    name: query
  selector:
    app: jaeger
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  name: jaeger-collector
  namespace: simple-app
spec:
  ports:
  - port: 4317
    targetPort: 4317
    name: grpc
  - port: 4318
    targetPort: 4318
    name: http
  - port: 14268
    targetPort: 14268
    name: binary
  selector:
    app: jaeger
  type: ClusterIP
```

Verifique se os pods estão executando. **kubectl get pods -n simple-app.**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759588540293/ee346d0a-35eb-4dd3-a94c-de99e4e1647c.png align="center")

Agora pra acessar a página web da sua aplicação faça **kubectl port-forward -n simple-app svc/jaeger-query 16686:16686**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759588672508/82977f5c-1b09-4e64-8621-93da9935e43f.png align="center")

Vamos agora fazer o deploy do OTLP Collector ele quem vai coletar os dados e enviar para o Backend.

**kubectl apply -f jaeger.yaml**

```yaml
# otel-collector.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: otel-collector
  namespace: simple-app
spec:
  mode: deployment
  image: otel/opentelemetry-collector-contrib:latest
  config: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318

    processors:
      batch:
        timeout: 10s
        send_batch_size: 1024
      memory_limiter:
        check_interval: 1s
        limit_mib: 512

    exporters:
      debug:
        verbosity: detailed
        sampling_initial: 5
        sampling_thereafter: 200
      otlp:
        endpoint: jaeger-collector:4317
        tls:
          insecure: true

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [otlp]
        metrics:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [debug]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [debug]
```

Agora vamos criar a configuração do Intrumentation. O Instrumentation é o **"template de configuração"** que diz ao Operator **como** instrumentar automaticamente suas aplicações sem modificar o código-fonte, definindo todos os parâmetros de configuração do OpenTelemetry de forma declarativa via Kubernetes.

Na documentação você pode verificar que é possível usar o Operator para instrumentar vários tipos de linguagem não só Python. Link: [https://github.com/open-telemetry/opentelemetry-operator](https://github.com/open-telemetry/opentelemetry-operator).

Vamos fazer o deploy **kubectl apply -f instrumentation.yaml**

```yaml
# instrumentation.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: flask-instrumentation
  namespace: simple-app
spec:  
  # propagators:
  #   - tracecontext
  #   - baggage
  # sampler:
  #   type: parentbased_traceidratio
  #   argument: "1.0"  # ou "0.25" para 25% sampling  
  python:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest
    env:
      - name: OTEL_TRACES_EXPORTER
        value: otlp
      # - name: OTEL_METRICS_EXPORTER
      #   value: otlp
      # - name: OTEL_LOGS_EXPORTER
      #   value: otlp
      # - name: OTEL_EXPORTER_OTLP_PROTOCOL
      #   value: http/protobuf
      # - name: OTEL_EXPORTER_OTLP_PROTOCOL
      #   value: grpc
      - name: OTEL_SERVICE_NAME
        value: flask-simple-app
      - name: OTEL_RESOURCE_ATTRIBUTES
        value: deployment.environment=development
      - name: OTEL_EXPORTER_OTLP_ENDPOINT
        value: http://otel-collector-collector-headless.simple-app.svc.cluster.local:4318
  exporter:
    endpoint: http://otel-collector-collector-headless.simple-app.svc.cluster.local:4318
```

Agora execute o comando **kubectl get instrumentation -n simple-app** para saber se seu instrumentation está devidamente configurado.

## **Deploy da aplicação**

Por último e mais importante, vamos fazer o deploy da aplicação. A parte mais importante e que faz toda essa mágica ser possível é essa configuração abaixo, através dela a aplicação e o instrumentation conseguem se comunicar e enviar dados para o collector.

```yaml
annotations:
        instrumentation.opentelemetry.io/inject-python: "simple-app/flask-instrumentation"
```

Para fazer o deploy iremos usar o comando **kubectl apply -f flask-app-deployment.yaml.**

```yaml
# flask-app-deployment.yaml - VERSÃO CORRIGIDA
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  namespace: simple-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
      annotations:
        instrumentation.opentelemetry.io/inject-python: "simple-app/flask-instrumentation"
    spec:
      containers:
      - name: flask-app
        image: joaochiroli123/app-flask:latest
        ports:
        - containerPort: 5000
          name: http
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: postgres-user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: postgres-password
        - name: POSTGRES_DB
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: postgres-db
        - name: POSTGRES_HOST
          value: "postgres" 
        - name: POSTGRES_PORT
          value: "5432"
        - name: FLASK_ENV
          value: "development"
        - name: FLASK_DEBUG
          value: "false"
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 10
          periodSeconds: 5
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: flask-app
  namespace: simple-app
spec:
  selector:
    app: flask-app
  ports:
  - name: http
    port: 80
    targetPort: 5000
  type: ClusterIP
```

Você pode verificar se os pods estão executando através do comando **kubectl get pods -n simple-app**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759589426372/ed347244-951f-42e6-8ca1-2446452046b3.png align="center")

Verificar os logs da aplicação se estão corretos. **kubectl logs &lt;nome do pod&gt; -n simple-app**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759589505004/ea2d8ba6-4f94-4f20-8386-e0768ce8f7e4.png align="center")

E o mais importante através do próximo comando é possivel verificar se a instrumentação foi feita na sua aplicação sem quebrar internamente alguma coisa **kubectl exec -n simple-app &lt;nome do pod&gt; -- python -c "import sys; print('\\n'.join(sys.path))"**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759589630795/9a2eca7d-f156-4008-aa73-924ba0f5da54.png align="center")

Para executar os comandos abaixo para testar nossa aplicação:

```yaml
curl http://localhost:5000/
curl http://localhost:5000/user/John
curl -X POST http://localhost:5000/submit \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello from curl!"}'
curl http://localhost:5000/health
curl -X POST http://localhost:5000/submit \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Complex request",
    "user": "admin",
    "priority": "high"
  }'
curl http://localhost:5000/09
curl http://localhost:5000/user/João
```

E pra ver ela na página web vamos executar. **kubectl port-forward -n simple-app svc/flask-app 5000:80**

Agora já é possível fazer algumas execuções na nossa página Web e verificar os traces chegando no Jaeger.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759589917770/bca76da0-2ea8-4144-83c1-de3414bcc546.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759589959528/4e8305a1-2d79-42e6-8edd-e90ddeb3aa2b.png align="center")

## Conclusão:

Neste artigo, exploramos como a auto-instrumentação utilizando o **OpenTelemetry Operator** simplifica significativamente a implementação de observabilidade em aplicações Python rodando em Kubernetes. Diferentemente da instrumentação manual, essa abordagem elimina a necessidade de modificar o código-fonte da aplicação ou gerenciar manualmente as dependências do OpenTelemetry.

Através do Operator, conseguimos:

* **Automatizar** a injeção de bibliotecas de instrumentação via init containers
    
* **Centralizar** as configurações de telemetria usando o recurso Instrumentation
    
* **Padronizar** a coleta de traces, métricas e logs em todo o cluster
    
* **Simplificar** o gerenciamento de observabilidade em ambientes Kubernetes
