first commit

This commit is contained in:
2026-04-29 20:45:00 -03:00
parent 0c4f67eb63
commit f613b58a8a
37 changed files with 1740 additions and 106 deletions

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/Nemo.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" default="true" project-jdk-name="24" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Nemo.iml" filepath="$PROJECT_DIR$/.idea/Nemo.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,24 @@
# Sample guideline, please follow similar structure for guideline with code samples
# 1. Suggest using streams instead of simple loops for better readability.
# <example>
# *Comment:
# Category: Minor
# Issue: Use streams instead of a loop for better readability.
# Code Block:
#
# ```java
# // Calculate squares of numbers
# List<Integer> squares = new ArrayList<>();
# for (int number : numbers) {
# squares.add(number * number);
# }
# ```
# Recommendation:
#
# ```java
# // Calculate squares of numbers
# List<Integer> squares = Arrays.stream(numbers)
# .map(n -> n * n) // Map each number to its square
# .toList();
# ```
# </example>

16
Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV OPENAI_API_KEY=sk-fake
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]

819
README.md
View File

@@ -1,179 +1,786 @@
# CI/CD para Artefatos de Configuração no NeMo Guardrails
# CI/CD para Agentes de IA com NeMo Guardrails como Package Python no Azure DevOps e Deploy em OCI OKE
## Introdução
## 1. Introdução
Em projetos que utilizam **NeMo Guardrails**, os principais artefatos não são apenas código, mas sim **configurações declarativas** que controlam o comportamento do LLM.
Este documento apresenta uma estratégia de CI/CD para projetos de **Agentes de IA** que consomem uma arquitetura compartilhada de **guardrails baseada em NeMo Guardrails**.
Isso muda completamente a abordagem de CI/CD.
A referência técnica e conceitual deste material é o projeto:
> [Tutorial: NeMo Guardrails com Python, Proxy OpenAI-Compatible e Tracing
](https://github.com/hoshikawa2/nemo_guardrails_configuration)
A decisão arquitetural considerada neste documento é:
> O projeto NeMo Guardrails **não será implantado como um serviço/server separado**.
> Ele será empacotado como uma **biblioteca Python versionada**, publicada por um pipeline próprio, e instalada pelos projetos de Agente de IA durante o build da imagem Docker.
Essa decisão reduz a complexidade operacional, preserva a cadência de desenvolvimento dos times de agentes e mantém a governança dos artefatos de guardrails por meio de versionamento, testes e controle de publicação no pipeline do projeto NeMo.
---
## Tipos de Artefatos no NeMo
## 2. Visão geral da arquitetura
### 1. Configurações principais
- `config.yml` / `rails.yaml`
- `guardrails.yaml`
- `prompts/`
- `flows.co`
- `input.co`
- `output.co`
### 2.1 Fluxo funcional dos guardrails
### 2. Código complementar
- Actions Python (`actions.py`)
- Integrações externas (APIs, tools)
---
## Problema Principal
Diferente de código tradicional:
- Pequenas mudanças em `.co` ou `.yaml` podem quebrar o comportamento
- Não há erro de compilação
- Falhas são **semânticas**, não sintáticas
---
## Pipeline CI/CD Recomendado
### Etapa 1: Validação Sintática
Validar YAML e estrutura:
```bash
yamllint .
```text
User Input
Input Rails
├─ Regex: PII Masking
├─ LLM: Toxicidade
└─ LLM: Out-of-Scope
LLM principal via NeMo Guardrails
Output Rails
├─ Compliance Anatel
├─ Verbalização Prematura
└─ Groundedness
Python Rules
├─ Alçada de Ajuste
└─ Consistência Histórica
Execução de API / Backend
Supervisor VAS Avulso
Curadoria / Métricas
├─ TCR
├─ Fallback
├─ Tokens
├─ Tamanho de mensagem
└─ Eficiência RAG
Resposta final
```
Validar carregamento:
Esse fluxo representa a função do projeto NeMo dentro da arquitetura: ele atua como uma **camada de segurança, validação, controle de resposta, curadoria e métricas** para agentes de IA.
---
## 3. Decisão arquitetural: NeMo como package, não como serviço
### 3.1 Modelo descartado: NeMo como serviço central
Uma alternativa seria implantar o projeto NeMo como um serviço separado:
```text
Agente de IA
↓ HTTP
Serviço NeMo Guardrails
LLM / Tools / Backend
```
Esse modelo centralizaria o runtime dos guardrails, mas traria custos e impactos:
- necessidade de manter um serviço adicional;
- aumento de latência;
- criação de dependência runtime entre agente e serviço de guardrails;
- maior complexidade de deploy;
- possível gargalo de aprovação e governança;
- risco de interromper a cadência dos times de desenvolvimento.
### 3.2 Modelo adotado: NeMo como biblioteca versionada
```text
Pipeline NeMo
gera package Python versionado
publica em um feed privado
Pipeline Agente
pip install company-nemo-guardrails==x.y.z
build da imagem final do agente
deploy no OCI OKE
```
No runtime, o Pod terá **um único container**, contendo:
```text
Pod Kubernetes
└── Container do Agente
├── código do agente de IA
├── package NeMo Guardrails instalado
├── configs de guardrails
├── actions
├── deterministic rails
└── llm rails
```
Portanto, o deploy no OKE é sempre da **imagem final do agente**, e não de uma imagem separada do NeMo.
---
## 4. Separação de responsabilidades
### 4.1 Projeto NeMo Guardrails
O projeto NeMo é responsável por:
- manter os artefatos de guardrails;
- implementar rails determinísticos;
- implementar rails baseados em LLM;
- registrar actions;
- manter prompts;
- manter catálogo de regras;
- executar testes unitários e de regressão;
- gerar package Python versionado;
- publicar o package em um feed privado.
### 4.2 Projeto Agente de IA
Cada projeto de Agente de IA é responsável por:
- implementar sua lógica conversacional;
- implementar integrações com APIs, backend, ferramentas e RAG;
- declarar a dependência do package NeMo;
- executar testes de integração com os guardrails;
- construir a imagem Docker final;
- publicar a imagem no OCIR;
- fazer deploy no OKE.
### 4.3 Azure DevOps
O Azure DevOps é responsável por orquestrar:
- pipeline do NeMo;
- pipeline dos agentes;
- publicação do package;
- build Docker;
- push para OCIR;
- deploy no OKE;
- controle de variáveis e secrets;
- permissões entre repositórios, feeds e pipelines.
---
## 5. Artefatos envolvidos no projeto NeMo
O projeto `nemo_guardrails_configuration` contém artefatos que devem ser tratados como componentes de uma biblioteca compartilhada.
Estrutura esperada:
```text
nemo_guardrails_tracing_project/
├── config/
│ ├── config.yml
│ ├── config.py
│ ├── guardrails.yaml
│ ├── guardrails_catalog.json
│ └── rails/
│ ├── input.co
│ └── output.co
├── src/
│ ├── app.py
│ ├── app_nemo.py
│ ├── actions.py
│ ├── deterministic_rails.py
│ ├── judges.py
│ ├── llm_client.py
│ ├── llm_rails.py
│ ├── models.py
│ ├── registry.py
│ └── prompts/
├── tests/
│ └── test_guardrails.py
├── scripts/
│ └── run_tests.sh
├── requirements.txt
├── pyproject.toml
├── .env.example
└── README.md
```
### 5.1 `config/config.yml`
Arquivo principal de configuração do NeMo Guardrails. Define modelos, engine, parâmetros, input rails, output rails e fluxos habilitados.
Em ambientes corporativos, o `engine: openai` pode apontar para um proxy OpenAI-compatible, inclusive um proxy interno para OCI Generative AI.
### 5.2 `config/rails/input.co`
Define fluxos de entrada, como PII, toxicidade, prompt injection, out-of-scope, bloqueio de intenção e normalização de input.
### 5.3 `config/rails/output.co`
Define fluxos de saída, como resposta segura, groundedness, ausência de vazamento de dados, aderência ao tom, compliance e prevenção de verbalização prematura.
### 5.4 `src/actions.py`
Arquivo de registro das actions expostas ao NeMo. Ele conecta os fluxos `.co` com funções Python.
### 5.5 `src/deterministic_rails.py`
Contém rails determinísticos, como máscara de CPF, validação de alçada, validação de protocolo, consistência entre valores e regras de negócio com retorno previsível.
### 5.6 `src/llm_rails.py`
Contém rails que dependem de LLM ou judge, como toxicidade, out-of-scope, groundedness, qualidade de resposta, tom de voz e avaliação semântica.
### 5.7 `src/judges.py`
Contém avaliadores usados para medir qualidade ou segurança das respostas.
### 5.8 `src/models.py`
Contém classes de retorno padronizadas, por exemplo:
```python
from nemoguardrails import LLMRails, RailsConfig
class RailResult:
allowed: bool
reason: str
sanitized_text: str | None
code: str
mechanism: str
data: dict
```
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
### 5.9 `src/registry.py`
Contém o registry dos guardrails, mapeando código da regra, nome, tipo, mecanismo, severidade e comportamento esperado.
### 5.10 `src/prompts/`
Contém prompts usados por rails baseados em LLM. Esses prompts devem ser tratados como artefatos sensíveis.
### 5.11 `tests/test_guardrails.py`
Contém a suíte de testes do projeto NeMo. O pipeline apenas executa os testes versionados no repositório.
### 5.12 `pyproject.toml`
Arquivo necessário para transformar o projeto NeMo em package Python instalável.
---
## 6. Segurança e governança dos artefatos NeMo
Como o projeto NeMo contém artefatos de segurança, ele deve ter governança própria.
### 6.1 O time do agente não deve ter acesso aos steps internos do pipeline NeMo
O time do agente deve consumir apenas o resultado aprovado:
```text
company-nemo-guardrails==1.2.0
```
Ele não precisa acessar YAML do pipeline NeMo, steps de teste, lógica de aprovação, scripts internos ou secrets do pipeline NeMo.
### 6.2 Permissões recomendadas no Azure DevOps
| Recurso | Time NeMo | Time Agente |
|---|---:|---:|
| Repo NeMo | Leitura/escrita | Sem acesso ou leitura restrita |
| Pipeline NeMo | Administração | Sem acesso |
| Feed do package | Publicação | Leitura |
| Repo Agente | Sem acesso ou leitura | Leitura/escrita |
| Pipeline Agente | Sem acesso ou leitura | Administração |
| Service Connection OCI/OKE | Uso controlado | Uso conforme necessidade |
### 6.3 Segurança por contrato
O contrato entre NeMo e Agente é o package.
```text
Package Python versionado = contrato técnico e de segurança
```
---
### Etapa 2: Testes de Guardrails (Unitários)
## 7. Fluxo de CI/CD completo
Criar cenários:
### 7.1 Pipeline do NeMo
```python
tests = [
{"input": "xingar atendente", "expected_blocked": True},
{"input": "cancelar plano", "expected_blocked": False}
```text
Commit no repo NeMo
Pipeline NeMo
instala dependências
executa testes
gera package Python
publica no Azure Artifacts
```
### 7.2 Pipeline do Agente
```text
Commit no repo Agente
Pipeline Agente
instala dependências
instala package NeMo
executa testes do agente
build Docker
push OCIR
deploy OKE
```
### 7.3 Quem chama quem?
Nenhum pipeline precisa chamar o outro diretamente. O acoplamento ocorre via package:
```text
Pipeline NeMo publica package
Pipeline Agente instala package
```
---
## 8. Criando o package Python do projeto NeMo
### 8.1 Estrutura sugerida
```text
nemo_guardrails_configuration/
├── company_nemo_guardrails/
│ ├── __init__.py
│ ├── config/
│ │ ├── config.yml
│ │ └── rails/
│ │ ├── input.co
│ │ └── output.co
│ ├── actions.py
│ ├── deterministic_rails.py
│ ├── llm_rails.py
│ ├── judges.py
│ ├── models.py
│ ├── registry.py
│ └── prompts/
├── tests/
├── pyproject.toml
└── README.md
```
### 8.2 Exemplo de `pyproject.toml`
```toml
[build-system]
requires = ["setuptools>=68", "wheel", "build"]
build-backend = "setuptools.build_meta"
[project]
name = "company-nemo-guardrails"
version = "1.0.0"
description = "Biblioteca corporativa de guardrails baseada em NeMo Guardrails"
requires-python = ">=3.11"
dependencies = [
"nemoguardrails[openai]",
"pydantic>=2",
"httpx"
]
[tool.setuptools.packages.find]
where = ["."]
[tool.setuptools.package-data]
company_nemo_guardrails = [
"config/*.yml",
"config/**/*.co",
"prompts/*.txt",
"prompts/*.md",
"*.json"
]
```
Executar:
### 8.3 Função utilitária para carregar config do package
```python
for t in tests:
response = rails.generate(messages=[{"role": "user", "content": t["input"]}])
from importlib.resources import files
from nemoguardrails import RailsConfig, LLMRails
def create_rails():
config_path = files("company_nemo_guardrails").joinpath("config")
config = RailsConfig.from_path(str(config_path))
return LLMRails(config)
```
Uso no agente:
```python
from company_nemo_guardrails import create_rails
rails = create_rails()
```
---
### Etapa 3: Testes de Regressão
## 9. Pipeline Azure DevOps do NeMo para publicar package
Garantir que mudanças não quebram comportamento anterior.
```yaml
trigger:
- main
- Snapshot de respostas
- Comparação antes/depois
pool:
vmImage: 'ubuntu-latest'
---
variables:
PYTHON_VERSION: '3.11'
### Etapa 4: Testes de Segurança (Guardrails)
steps:
- checkout: self
Validar:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
displayName: 'Use Python $(PYTHON_VERSION)'
- Prompt injection
- Toxicidade
- Out-of-scope
- script: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install build twine pytest
displayName: 'Install dependencies'
---
- script: |
export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-fake}"
pytest -v --junitxml=test-results.xml
displayName: 'Run NeMo Guardrails tests'
### Etapa 5: Testes de Fluxo Completo (E2E)
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'test-results.xml'
failTaskOnFailedTests: true
condition: succeededOrFailed()
displayName: 'Publish test results'
Simular jornada real:
- script: |
python -m build
ls -la dist
displayName: 'Build Python package'
- Input → Guardrails → LLM → Tools → Output
- task: TwineAuthenticate@1
inputs:
artifactFeed: 'company-python-feed'
displayName: 'Authenticate Azure Artifacts feed'
---
## Estratégia de Versionamento
### Versionar separadamente:
- Código
- Configuração de guardrails
Exemplo:
```
/app
/config
/tests
- script: |
python -m twine upload --repository company-python-feed --config-file $(PYPIRC_PATH) dist/*
displayName: 'Publish package to Azure Artifacts'
```
---
## Deploy
## 10. Projeto Agente consumindo o package NeMo
### Opção 1: Embutido no container
### 10.1 Estrutura do projeto Agente
```Dockerfile
COPY config/ /app/config/
```text
ai_agent_project/
├── app/
│ ├── main.py
│ └── agent.py
├── tests/
│ ├── test_agent.py
│ └── test_guardrails_integration.py
├── k8s/
│ ├── deployment.yaml
│ └── service.yaml
├── Dockerfile
├── requirements.txt
├── azure-pipelines.yml
└── README.md
```
### Opção 2: Config externo
### 10.2 `requirements.txt` do Agente
- S3 / OCI Object Storage
- Volume mount
```txt
fastapi
uvicorn[standard]
company-nemo-guardrails==1.0.0
```
### 10.3 Uso no código do Agente
```python
from company_nemo_guardrails import create_rails
rails = create_rails()
def process_message(message: str):
response = rails.generate(
messages=[{"role": "user", "content": message}],
options={"log": {"activated_rails": True}}
)
return response.output_text
```
---
## Observabilidade no CI/CD
## 11. Pipeline Azure DevOps do Agente
Integrar com:
```yaml
trigger:
- main
- Logs estruturados
- Tracing (OpenTelemetry)
- Métricas de bloqueio
pool:
vmImage: 'ubuntu-latest'
variables:
PYTHON_VERSION: '3.11'
IMAGE_NAME: 'ai-agent'
IMAGE_TAG: '$(Build.SourceVersion)'
K8S_NAMESPACE: 'ai-agent'
steps:
- checkout: self
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
displayName: 'Use Python $(PYTHON_VERSION)'
- task: PipAuthenticate@1
inputs:
artifactFeeds: 'company-python-feed'
displayName: 'Authenticate Python feed'
- script: |
python -m pip install --upgrade pip
pip install -r requirements.txt
displayName: 'Install agent dependencies with NeMo package'
- script: |
export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-fake}"
pytest -v --junitxml=test-results.xml
displayName: 'Run agent tests'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'test-results.xml'
failTaskOnFailedTests: true
condition: succeededOrFailed()
displayName: 'Publish test results'
- script: |
IMAGE_URI="$(OCIR_REGISTRY)/$(OCI_NAMESPACE)/$(OCIR_REPOSITORY):$(IMAGE_TAG)"
echo "##vso[task.setvariable variable=IMAGE_URI]$IMAGE_URI"
docker build -t "$IMAGE_URI" .
displayName: 'Build Docker image'
- script: |
echo "$(OCIR_PASSWORD)" | docker login "$(OCIR_REGISTRY)" -u "$(OCIR_USER)" --password-stdin
docker push "$(IMAGE_URI)"
displayName: 'Push image to OCIR'
- script: |
mkdir -p ~/.kube
echo "$(OKE_KUBECONFIG_B64)" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
kubectl apply -f k8s/namespace.yaml
sed "s|REPLACE_IMAGE_URI|$(IMAGE_URI)|g" k8s/deployment.yaml | kubectl apply -f -
kubectl apply -f k8s/service.yaml
kubectl rollout status deployment/ai-agent -n $(K8S_NAMESPACE) --timeout=180s
displayName: 'Deploy to OCI OKE'
```
---
## Estratégia Avançada
## 12. Dockerfile do Agente
### Feature Flags de Guardrails
```dockerfile
FROM python:3.11-slim
Permitir ativar/desativar regras sem deploy.
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
```
---
## Anti-patterns
## 13. Variáveis e secrets no Azure DevOps
Evitar:
### 13.1 Variáveis do pipeline do Agente
- Deploy direto sem testes
- Misturar lógica de negócio com guardrails
- Não versionar `.co`
| Variável | Exemplo | Tipo |
|---|---|---|
| `OCIR_REGISTRY` | `gru.ocir.io` | normal |
| `OCI_NAMESPACE` | `meunamespace` | normal |
| `OCIR_REPOSITORY` | `agents/ai-agent` | normal |
| `OCIR_USER` | `tenancy/user` | secret |
| `OCIR_PASSWORD` | auth token OCI | secret |
| `OKE_KUBECONFIG_B64` | kubeconfig em base64 | secret |
| `OPENAI_API_KEY` | chave ou fake/proxy | secret |
### 13.2 Variáveis do pipeline NeMo
| Variável | Exemplo | Tipo |
|---|---|---|
| `OPENAI_API_KEY` | `sk-fake` | secret |
| Feed credentials | gerenciado pelo Azure Artifacts | service/task |
---
## Conclusão
## 14. OCI Vault
No NeMo Guardrails:
### 14.1 Papel do OCI Vault
> CI/CD não é só sobre código, é sobre comportamento do agente.
O OCI Vault deve ser usado para proteger secrets em runtime:
É essencial tratar configurações como **artefatos críticos de software**.
- chaves de LLM;
- credenciais de API;
- tokens de backend;
- senhas de integração.
## Referências
### 14.2 Estratégia simples
- [Tutorial: NeMo Guardrails com Python, Proxy OpenAI-Compatible e Tracing](https://github.com/hoshikawa2/nemo_guardrails_configuration)
```text
Azure DevOps secret → Kubernetes Secret → Pod
```
## Acknowledgments
### 14.3 Estratégia recomendada
- **Author** - Cristiano Hoshikawa (Oracle LAD A-Team Solution Engineer)
```text
OCI Vault → External Secrets Operator / CSI Driver → Kubernetes Secret → Pod
```
Esse modelo evita manter secrets de runtime no Azure DevOps.
---
## 15. Segurança: impedindo acesso indevido ao pipeline NeMo
### 15.1 Separação de repositórios
```text
Repo NeMo → time plataforma/guardrails
Repo Agente → time aplicação/agente
```
### 15.2 Separação de pipelines
```text
Pipeline NeMo → administrado pelo time NeMo
Pipeline Agente → administrado pelo time Agente
```
### 15.3 Compartilhamento apenas do package
O time do agente recebe permissão de leitura no feed:
```text
company-python-feed
```
Ele não precisa acessar repo NeMo, pipeline NeMo, steps de teste, scripts internos ou secrets do NeMo.
---
## 16. Testes no modelo com package
### 16.1 Testes do NeMo
Executados no pipeline NeMo:
- testes determinísticos;
- testes de segurança;
- testes de regressão;
- testes de catálogo;
- testes de actions;
- testes de judge.
### 16.2 Testes do Agente
Executados no pipeline do agente:
- testes do código do agente;
- teste de carregamento do package NeMo;
- teste de integração mínima;
- teste E2E do fluxo conversacional;
- teste de contrato de API.
### 16.3 Exemplo
```python
def test_agent_uses_guardrails():
from company_nemo_guardrails import create_rails
rails = create_rails()
assert rails is not None
```
---
## 17. Versionamento
Recomenda-se versionamento semântico:
```text
MAJOR.MINOR.PATCH
```
Exemplos:
```text
1.0.0
1.1.0
1.1.1
2.0.0
```
| Tipo de mudança | Versão |
|---|---|
| Correção sem mudança de comportamento esperado | PATCH |
| Nova regra compatível | MINOR |
| Mudança que pode bloquear novos casos | MINOR ou MAJOR |
| Mudança incompatível | MAJOR |
No agente:
```txt
company-nemo-guardrails==1.2.0
```
Ou aceitando patches:
```txt
company-nemo-guardrails~=1.2.0
```
---
## 18. Conclusão
A estratégia recomendada para este cenário é:
```text
NeMo Guardrails = package Python versionado
Agente de IA = aplicação final
Pipeline NeMo = publica package aprovado
Pipeline Agente = instala package, gera imagem e faz deploy
Runtime = um container com agente + guardrails
```
Esse modelo equilibra segurança, governança, velocidade, simplicidade operacional, reutilização entre múltiplos agentes e deploy único no OCI OKE.
A governança deixa de depender de um serviço central em runtime e passa a acontecer no ciclo de CI/CD, por meio de testes, versionamento, publicação controlada e permissões no feed de packages.

462
README_BKP.md Normal file
View File

@@ -0,0 +1,462 @@
# CI/CD NeMo Guardrails no Azure DevOps com Deploy em OCI OKE
## Objetivo
Esta documentação tem por objetivo mostrar como implantar a estratégia de guardrails via Nemo Guardrails.
A idéia principal é que o projeto está baseado nesta especificação:
[Tutorial: NeMo Guardrails com Python, Proxy OpenAI-Compatible e Tracing](https://github.com/hoshikawa2/nemo_guardrails_configuration)
A arquitetura desta solução visa se integrar com soluções de agentes de IA com os guardrails do Nemo Guardrails.
```text
User Input
Input Rails
├─ Regex: PII Masking
├─ LLM: Toxicidade
└─ LLM: Out-of-Scope
LLM principal via NeMo Guardrails
Output Rails
├─ Compliance Anatel
├─ Verbalização Prematura
└─ Groundedness
Python Rules
├─ Alçada de Ajuste
└─ Consistência Histórica
Execução de API / Backend
Supervisor VAS Avulso
Curadoria / Métricas
├─ TCR
├─ Fallback
├─ Tokens
├─ Tamanho de mensagem
└─ Eficiência RAG
Resposta final
```
Os detalhes da arquitetura podem ser vistas no material citado acima.
As aplicações agentes devem ser integradas a esta estrutura do projeto:
```text
ai_agent_project
├── src/
│ ...
nemo_guardrails_tracing_project/
├── config/
│ ├── config.yml : Arquivo de configuração para o Nemo Guardrail (Modelos, definições de rails de input e output)
│ ├── config.py : Registro das ações para os guardrails
│ ├── guardrails.yaml : Arquivo de mapeamento dos rails conforme definição da Planilha inicial (não utilizado pelo código)
│ ├── rails/
│ ├─────input.co : configuração de entrada dos fluxos Nemo
│ ├─────output.co : configuração de saída dos fluxos Nemo
│ └── guardrails_catalog.json
├── src/
│ ├── app.py : demo para consumir rails sem uso de Nemo Guardrails
│ ├── app_nemo.py : demo para consumir rails com uso de Nemo Guardrails
│ ├── actions.py : Arquivo de Actions expondo deterministic_rails e llm_rails
│ ├── deterministic_rails.py : rails deterministicos para serem utilizados como modelo na estrutura
│ ├── judges.py : estrutura demo para ilustrar judge
│ ├── llm_client.py : mockup para demo llm
│ ├── llm_rails.py : rails llm para serem utilizados como modelo na estrutura
│ ├── models.py : classe do modelo de resposta dos rails
│ ├── registry.py : modelo de registry do guardrail
│ └── prompts/ : pasta com os prompts para rails que utilizam llm
├── tests/
│ └── test_guardrails.py : código utilizado para pytest
├── scripts/
│ └── run_tests.sh : bash script para testar os rails individualmente via pytest
├── requirements.txt : bibliotecas necessárias para o projeto
├── .env.example : variáveis de ambiente para configuração
└── README.md
```
Portanto, trataremos aqui neste material a implantação da estrutura de **nemo_guardrails_tracing_project** juntamente com o projeto de **Agente de IA**.
Conceitualmente, teríamos este pipeline de deployment:
```text
Pipeline do Agente de IA
aciona/consome
Pipeline do projeto NeMo Guardrails
publica pacote/artefato aprovado
Pipeline do Agente usa somente o resultado
```
O pipeline do agente não vê nem controla os steps internos do NeMo. Ele só consome um artefato versionado/aprovado.
## Modelo recomendado
### 1. Pipeline separado do NeMo
No repositório nemo_guardrails_configuration, você cria um pipeline próprio que:
```text
checkout nemo
instala dependências
roda testes de guardrails
gera pacote/artefato
publica artifact ou package
```
- O NeMo NÃO publica uma imagem final para produção
- Ele publica um artefato reutilizável
Exemplo conceitual:
O alvo do deployment é um container dentro de um cluster Kubernetes. Portanto, a idéia é:
```text
Pod
└── container único
├── código do agente
├── library do NeMo
└── configs de guardrails
```
No modelo de library, teremos o seguinte:
```text
Pipeline NeMo
gera artifact (ou package)
-----------------------------------
Pipeline Agente
baixa artifact do NeMo
EMBUTE no build
gera imagem FINAL
deploy no OKE
```
**azure-pipelines-nemo.yml**
```yaml
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- task: UsePythonVersion@0
inputs:
versionSpec: '3.11'
- script: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pytest -v
displayName: Test NeMo Guardrails
- script: |
mkdir -p dist/nemo_guardrails_configuration
cp -r config dist/nemo_guardrails_configuration/
cp -r src dist/nemo_guardrails_configuration/
cp requirements.txt dist/nemo_guardrails_configuration/
displayName: Package NeMo artifacts
- publish: dist/nemo_guardrails_configuration
artifact: nemo-guardrails-package
displayName: Publish NeMo package
```
Esse pipeline é controlado pelo time responsável pelo NeMo.
A arquitetura foi pensada para um projeto de guardrails que não é uma aplicação final isolada, mas uma camada reutilizável consumida por várias soluções, agentes, bots, APIs e motores conversacionais.
---
## Arquitetura
```text
Azure DevOps
|
|-- checkout do repositório
|-- instala Python 3.11
|-- instala dependências
|-- executa pytest
|-- build Docker
|-- push para OCIR
|-- kubectl apply no OKE
|
OCI OKE
|
|-- Deployment guardrails-api
|-- Service guardrails-service
|
Aplicações consumidoras
|
|-- HTTP POST /chat
```
---
## Estrutura de arquivos
```text
.
├── app/
│ ├── __init__.py
│ └── main.py
├── config/
│ ├── config.yml
│ ├── input.co
│ └── output.co
├── src/
│ ├── __init__.py
│ ├── actions.py
│ ├── deterministic_rails.py
│ └── llm_rails.py
├── tests/
│ ├── conftest.py
│ ├── test_api_contract.py
│ ├── test_e2e.py
│ └── test_structural.py
├── k8s/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
├── scripts/
│ ├── build_local.sh
│ ├── deploy_local.sh
│ └── test_local.sh
├── azure-pipelines.yml
├── Dockerfile
├── requirements.txt
└── README.md
```
---
## O que é o `./config` no CI/CD?
No código NeMo Guardrails normalmente existe:
```python
from nemoguardrails import LLMRails, RailsConfig
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
```
No Azure DevOps, o `./config` é o diretório `config/` versionado no repositório.
Quando o pipeline executa:
```yaml
- checkout: self
```
o repositório é baixado no agente do Azure DevOps. A partir daí, o diretório `config/` passa a existir no workspace do pipeline.
Para evitar problemas com diretório corrente, este projeto usa path absoluto relativo ao arquivo Python:
```python
BASE_DIR = Path(__file__).resolve().parent.parent
CONFIG_PATH = BASE_DIR / "config"
```
Assim, funciona localmente, no pytest, no Docker e no OKE.
---
## Artefatos principais
### `config/config.yml`
Define o modelo principal usado pelo NeMo Guardrails.
Neste exemplo usamos `engine: openai` porque muitos frameworks esperam a interface OpenAI. Em ambientes reais, esse endpoint pode apontar para um proxy OpenAI-compatible, inclusive um proxy para OCI Generative AI.
### `config/input.co`
Define rails de entrada. Em um projeto real, aqui entram fluxos de validação de entrada, por exemplo:
- toxicidade
- prompt injection
- dados sensíveis
- fora de escopo
### `config/output.co`
Define rails de saída. Em um projeto real, aqui entram validações como:
- resposta segura
- resposta aderente ao contexto
- resposta sem vazamento de dados
- resposta sem alucinação
### `src/deterministic_rails.py`
Contém regras determinísticas. Exemplos:
- máscara de CPF
- validação de alçada
- checagem de protocolo
- normalização de texto
### `src/llm_rails.py`
Contém validações que podem depender de LLM ou judge. Exemplos:
- qualidade de resposta
- groundedness
- classificação semântica
- verificação de tom
### `src/actions.py`
Local para registrar actions usadas pelo NeMo Guardrails.
### `app/main.py`
Expõe a solução como uma API HTTP:
```text
GET /health
POST /chat
```
Isso permite que várias soluções consumam a arquitetura central de guardrails.
---
## Execução local
```bash
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export OPENAI_API_KEY=sk-fake
pytest -v
uvicorn app.main:app --host 0.0.0.0 --port 8080
```
Teste:
```bash
curl -X POST http://localhost:8080/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"quero cancelar plano"}]}'
```
---
## Docker local
```bash
docker build -t guardrails-api:local .
docker run --rm -p 8080:8080 -e OPENAI_API_KEY=sk-fake guardrails-api:local
```
---
## Variáveis necessárias no Azure DevOps
Configure estas variáveis no Pipeline ou em Variable Group:
| Variável | Exemplo | Observação |
|---|---|---|
| `OCIR_REGION_KEY` | `gru` ou `sa-saopaulo-1` | usado para montar registry |
| `OCIR_REGISTRY` | `gru.ocir.io` | endpoint OCIR |
| `OCI_NAMESPACE` | `meunamespace` | namespace Object Storage/OCIR |
| `OCIR_REPOSITORY` | `guardrails/guardrails-api` | repositório da imagem |
| `OCIR_USER` | `tenancy/user` | usuário OCIR |
| `OCIR_PASSWORD` | `***` | auth token OCI |
| `OKE_KUBECONFIG_B64` | `***` | kubeconfig em base64 |
| `OPENAI_API_KEY` | `sk-fake` | pode ser fake quando usar proxy |
---
## Como gerar o kubeconfig base64
Na sua máquina:
```bash
oci ce cluster create-kubeconfig \
--cluster-id <OCID_DO_CLUSTER> \
--file kubeconfig \
--region sa-saopaulo-1 \
--token-version 2.0.0
base64 -i kubeconfig | pbcopy
```
Cole o valor em `OKE_KUBECONFIG_B64` no Azure DevOps.
---
## Pipeline Azure DevOps
O arquivo `azure-pipelines.yml` faz:
1. Checkout.
2. Setup Python 3.11.
3. Instala dependências.
4. Executa testes.
5. Build da imagem Docker.
6. Login no OCIR.
7. Push da imagem.
8. Aplica manifests Kubernetes no OKE.
9. Aguarda rollout.
---
## Deploy no OKE
Os manifests estão em `k8s/`.
O pipeline substitui a imagem no deployment usando:
```bash
kubectl set image deployment/guardrails-api guardrails-api=$IMAGE_URI -n guardrails
```
---
## Testes
Os testes são versionados no repositório. O CI/CD não cria os testes; ele apenas executa.
Este pacote inclui:
- teste estrutural: valida carregamento do `RailsConfig`.
- teste de contrato da API: valida formato da requisição/resposta.
- teste E2E: simula entrada passando por API e rails.
---
## Considerações de produção
Para produção, recomenda-se evoluir:
- Secrets via Kubernetes Secret ou OCI Vault.
- Ingress Controller em vez de LoadBalancer direto.
- HPA para autoscaling.
- Logs estruturados.
- Tracing com OpenTelemetry.
- Separação de testes determinísticos e testes com LLM.
- Política de versionamento de imagem por commit SHA.

0
app/__init__.py Normal file
View File

56
app/main.py Normal file
View File

@@ -0,0 +1,56 @@
import os
from pathlib import Path
from fastapi import FastAPI
from pydantic import BaseModel
from nemoguardrails import LLMRails, RailsConfig
from src.deterministic_rails import mask_pii, detectar_toxicidade, detectar_out_of_scope
BASE_DIR = Path(__file__).resolve().parent.parent
CONFIG_PATH = BASE_DIR / "config"
os.environ.setdefault("OPENAI_API_KEY", "sk-fake")
config = RailsConfig.from_path(str(CONFIG_PATH))
rails = LLMRails(config)
app = FastAPI(title="Guardrails API", version="1.0.0")
class ChatRequest(BaseModel):
messages: list[dict]
@app.get("/health")
def health():
return {"status": "ok", "config_path": str(CONFIG_PATH)}
@app.post("/chat")
def chat(payload: ChatRequest):
user_text = ""
for msg in reversed(payload.messages):
if msg.get("role") == "user":
user_text = msg.get("content", "")
break
pii = mask_pii(user_text)
tox = detectar_toxicidade(user_text)
scope = detectar_out_of_scope(user_text)
blocked = not tox.allowed
if blocked:
return {
"blocked": True,
"reason": tox.reason,
"rails": [pii.__dict__, tox.__dict__, scope.__dict__],
"output": "Mensagem bloqueada por política de segurança."
}
# Para demo CI/CD, evitamos depender de chamada real ao LLM.
# Em produção, troque para rails.generate(...).
output = f"Solicitação recebida e validada: {pii.sanitized_text}"
return {
"blocked": False,
"reason": "Allowed",
"rails": [pii.__dict__, tox.__dict__, scope.__dict__],
"output": output
}

63
azure-pipelines.yml Normal file
View File

@@ -0,0 +1,63 @@
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
PYTHON_VERSION: '3.11'
IMAGE_NAME: 'guardrails-api'
IMAGE_TAG: '$(Build.SourceVersion)'
K8S_NAMESPACE: 'guardrails'
steps:
- checkout: self
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
displayName: 'Use Python $(PYTHON_VERSION)'
- script: |
python -m pip install --upgrade pip
pip install -r requirements.txt
displayName: 'Install Python dependencies'
- script: |
export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-fake}"
pytest -v --junitxml=test-results.xml
displayName: 'Run pytest'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'test-results.xml'
failTaskOnFailedTests: true
condition: succeededOrFailed()
displayName: 'Publish test results'
- script: |
IMAGE_URI="$(OCIR_REGISTRY)/$(OCI_NAMESPACE)/$(OCIR_REPOSITORY):$(IMAGE_TAG)"
echo "##vso[task.setvariable variable=IMAGE_URI]$IMAGE_URI"
docker build -t "$IMAGE_URI" .
displayName: 'Build Docker image'
- script: |
echo "$(OCIR_PASSWORD)" | docker login "$(OCIR_REGISTRY)" -u "$(OCIR_USER)" --password-stdin
docker push "$(IMAGE_URI)"
displayName: 'Push Docker image to OCIR'
- script: |
mkdir -p ~/.kube
echo "$(OKE_KUBECONFIG_B64)" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secret.yaml
sed "s|REPLACE_IMAGE_URI|$(IMAGE_URI)|g" k8s/deployment.yaml | kubectl apply -f -
kubectl apply -f k8s/service.yaml
kubectl rollout status deployment/guardrails-api -n $(K8S_NAMESPACE) --timeout=180s
kubectl get svc guardrails-service -n $(K8S_NAMESPACE)
displayName: 'Deploy to OCI OKE'

16
config/config.yml Normal file
View File

@@ -0,0 +1,16 @@
models:
- type: main
engine: openai
model: gpt-4o-mini
parameters:
api_key: ${OPENAI_API_KEY}
base_url: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
rails:
input:
flows:
- self check input
output:
flows:
- self check output

5
config/input.co Normal file
View File

@@ -0,0 +1,5 @@
define flow self check input
$allowed = execute check_input
if not $allowed
bot refuse to respond
stop

5
config/output.co Normal file
View File

@@ -0,0 +1,5 @@
define flow self check output
$allowed = execute check_output
if not $allowed
bot refuse to respond
stop

39
k8s/deployment.yaml Normal file
View File

@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guardrails-api
namespace: guardrails
spec:
replicas: 2
selector:
matchLabels:
app: guardrails-api
template:
metadata:
labels:
app: guardrails-api
spec:
containers:
- name: guardrails-api
image: REPLACE_IMAGE_URI
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: guardrails-secrets
key: OPENAI_API_KEY
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 20
periodSeconds: 20

5
k8s/kustomization.yaml Normal file
View File

@@ -0,0 +1,5 @@
resources:
- namespace.yaml
- secret.yaml
- deployment.yaml
- service.yaml

4
k8s/namespace.yaml Normal file
View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: guardrails

8
k8s/secret.yaml Normal file
View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: guardrails-secrets
namespace: guardrails
type: Opaque
stringData:
OPENAI_API_KEY: sk-fake

13
k8s/service.yaml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: guardrails-service
namespace: guardrails
spec:
type: LoadBalancer
selector:
app: guardrails-api
ports:
- name: http
port: 80
targetPort: 8080

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
fastapi==0.115.6
uvicorn[standard]==0.34.0
nemoguardrails[openai]
pytest==8.3.4
httpx==0.28.1
pydantic>=2

3
scripts/build_local.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
set -euo pipefail
docker build -t guardrails-api:local .

4
scripts/deploy_local.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
kubectl apply -k k8s
kubectl rollout status deployment/guardrails-api -n guardrails

4
scripts/test_local.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
export OPENAI_API_KEY=${OPENAI_API_KEY:-sk-fake}
pytest -v

0
src/__init__.py Normal file
View File

15
src/actions.py Normal file
View File

@@ -0,0 +1,15 @@
from src.deterministic_rails import detectar_toxicidade, detectar_out_of_scope
def check_input(context=None):
text = ""
if context and "user_message" in context:
text = context["user_message"]
tox = detectar_toxicidade(text)
if not tox.allowed:
return False
return True
def check_output(context=None):
return True

View File

@@ -0,0 +1,44 @@
import re
from dataclasses import dataclass
@dataclass
class RailResult:
allowed: bool
reason: str = ""
sanitized_text: str | None = None
code: str | None = None
mechanism: str = "deterministic"
data: dict | None = None
def mask_pii(text: str) -> RailResult:
cpf_pattern = r"\b\d{3}\.\d{3}\.\d{3}-\d{2}\b"
sanitized = re.sub(cpf_pattern, "[CPF_MASCARADO]", text)
return RailResult(
allowed=True,
reason="PII masked when present.",
sanitized_text=sanitized,
code="MSK",
data={"pii_found": sanitized != text},
)
def detectar_toxicidade(text: str) -> RailResult:
toxic_terms = ["vai se ferrar", "idiota", "porcaria"]
is_toxic = any(t in text.lower() for t in toxic_terms)
return RailResult(
allowed=not is_toxic,
reason="Toxic content detected." if is_toxic else "No toxic content detected.",
sanitized_text=text,
code="TOX",
data={"toxic": is_toxic},
)
def detectar_out_of_scope(text: str) -> RailResult:
allowed_terms = ["plano", "cancelar", "fatura", "serviço", "atendimento", "protocolo"]
is_allowed = any(t in text.lower() for t in allowed_terms)
return RailResult(
allowed=is_allowed,
reason="Out of scope." if not is_allowed else "In scope.",
sanitized_text=text,
code="OOS",
data={"in_scope": is_allowed},
)

18
src/llm_rails.py Normal file
View File

@@ -0,0 +1,18 @@
from dataclasses import dataclass
@dataclass
class JudgeResult:
allowed: bool
score: int
reason: str
mechanism: str = "llm_judge"
def avaliar_qualidade_resposta(pergunta: str, resposta: str) -> JudgeResult:
# Exemplo determinístico para CI.
# Em ambiente real, pode chamar um LLM judge.
score = 5 if len(resposta.strip()) > 30 else 3
return JudgeResult(
allowed=score >= 4,
score=score,
reason="Resposta avaliada por critério simplificado para CI.",
)

16
tests/conftest.py Normal file
View File

@@ -0,0 +1,16 @@
import os
import pytest
from pathlib import Path
from nemoguardrails import RailsConfig, LLMRails
os.environ.setdefault("OPENAI_API_KEY", "sk-fake")
@pytest.fixture(scope="session")
def project_root():
return Path(__file__).resolve().parent.parent
@pytest.fixture(scope="session")
def rails(project_root):
config_path = project_root / "config"
config = RailsConfig.from_path(str(config_path))
return LLMRails(config)

View File

@@ -0,0 +1,19 @@
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health():
r = client.get("/health")
assert r.status_code == 200
assert r.json()["status"] == "ok"
def test_chat_allowed_contract():
r = client.post("/chat", json={
"messages": [{"role": "user", "content": "quero cancelar plano"}]
})
assert r.status_code == 200
body = r.json()
assert "blocked" in body
assert "output" in body
assert "rails" in body

21
tests/test_e2e.py Normal file
View File

@@ -0,0 +1,21 @@
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_e2e_toxicidade_bloqueada():
r = client.post("/chat", json={
"messages": [{"role": "user", "content": "vai se ferrar"}]
})
assert r.status_code == 200
body = r.json()
assert body["blocked"] is True
def test_e2e_pii_mascarado():
r = client.post("/chat", json={
"messages": [{"role": "user", "content": "Meu CPF é 169.323.728-86 e quero cancelar plano"}]
})
assert r.status_code == 200
body = r.json()
assert body["blocked"] is False
assert "169.323.728-86" not in body["output"]

7
tests/test_structural.py Normal file
View File

@@ -0,0 +1,7 @@
def test_config_loads(rails):
assert rails is not None
def test_config_directory_exists(project_root):
assert (project_root / "config" / "config.yml").exists()
assert (project_root / "config" / "input.co").exists()
assert (project_root / "config" / "output.co").exists()

115
tutorial_azure_oci_oke.md Normal file
View File

@@ -0,0 +1,115 @@
# 🔐 Tutorial Completo — Configuração de Credenciais OCI no Azure DevOps (CI/CD com OKE)
## 📌 Introdução
Este documento descreve como configurar credenciais para integração entre:
- Azure DevOps (CI/CD)
- Oracle Cloud Infrastructure (OCI)
- OCI Registry (OCIR)
- Oracle Kubernetes Engine (OKE)
- OCI Vault
---
## 🧱 Arquitetura
Azure DevOps → Docker → OCIR → OKE → Aplicação
---
## ⚙️ ETAPA 1 — Variáveis no Azure DevOps
Pipeline → Variables ou Library → Variable Groups
Variáveis:
- OCIR_USER (secret)
- OCIR_PASSWORD (secret)
- OCIR_REGISTRY
- OCI_NAMESPACE
- OCIR_REPOSITORY
- OPENAI_API_KEY (secret)
- OKE_KUBECONFIG_B64 (secret)
---
## ☸️ ETAPA 2 — Kubernetes (OKE)
### Gerar kubeconfig
oci ce cluster create-kubeconfig \
--cluster-id <OCID_CLUSTER> \
--file kubeconfig \
--region sa-saopaulo-1 \
--token-version 2.0.0
### Converter para base64
base64 kubeconfig
Salvar em OKE_KUBECONFIG_B64
### Uso no pipeline
echo "$(OKE_KUBECONFIG_B64)" | base64 -d > ~/.kube/config
---
## 🐳 ETAPA 3 — OCIR
Gerar Auth Token no OCI
User → Auth Tokens
Pipeline:
echo "$(OCIR_PASSWORD)" | docker login "$(OCIR_REGISTRY)" \
-u "$(OCIR_USER)" --password-stdin
---
## 🔐 ETAPA 4 — OCI Vault
Criar secret no OCI Vault
Security → Vault → Secrets
Criar Dynamic Group:
ALL {{resource.type = 'instance'}}
Criar Policy:
Allow dynamic-group <group> to read secret-bundles in compartment <compartment>
Buscar secret:
oci secrets secret-bundle get \
--secret-id <OCID_SECRET> \
--query "data.\"secret-bundle-content\".content" \
--raw-output | base64 --decode
---
## 🔄 Pipeline exemplo
pip install -r requirements.txt
pytest -v
docker build
docker push
kubectl apply
---
## ⚠️ Boas práticas
- Não commitar secrets
- Usar variáveis secret
- Usar Vault para produção
---
## 🚀 Conclusão
Pipeline seguro com Azure DevOps + OCI + OKE.