Processando milhões de mensagens com Python e Kubernetes
BIANCA ROSA
tech lead @ stone
Vamos falar sobre...
- Arquiteturas Escaláveis
- O nosso problema
- A nossa solução
- Dificuldades
Arquiteturas Escaláveis
Nasce um sistema!
E cresce um sistema!
Quando vamos ver...
O problema do forte acoplamento
- Lentidão pra processar uma ação
- Podem pertencer a times diferentes
- O que acontece se algum deles falha?
Evite arquiteturas com forte acoplamento.
Elas não escalam.
Ganhando escalabilidade + tolerância a falha
O nosso problema
8MM mensagens / dia
Disponibilidade em Tempo Real
MVP disponível rápido
Informação disponível no EventHub
Contrato podia mudar
A nossa solução
Python
+
Firestore
+
Azure Blob Storage
+
Kubernetes
Python
- Tempo curto
- Contrato podia mudar
- Linguagem que o time dominava em comum
Firestore
- Contrato podia mudar - NoSQL
- Banco gerenciado (NoOps)
- Aplicação iria ficar hospedada dentro do GCP
- Dá pra brincar com WebSocket
Azure Blob Storage
- EventHub exige que a gente faça controle de offset
- Lib pronta da Microsoft
- Não tinha tempo pra escrever uma lib que funcionasse com uma solução no GCP
Kubernetes
- Fácil pra escalar o consumo manualmente
- Alta disponibilidade e confiança
- Autoscaling configurável
- Extensível pra chegar numa solução robusta
FROM python:3.7
RUN pip install pipenv
COPY . /app
WORKDIR /app
RUN pipenv install --system --deploy --ignore-pipfile
CMD ["make", "run"]
-include .env
export
run:
newrelic-admin run-program python main.py
test:
pytest tests/unit
integration-test:
pytest tests/integration
coverage:
pytest tests/unit --doctest-modules --junitxml=junit/test-results.xml --cov=app --cov-report=xml --cov-report=html
lint:
pylint app
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-python-app-consumer
spec:
replicas: 8
template:
metadata:
labels:
app: my-python-app-consumer
spec:
containers:
- name: my-python-app-consumer
image: MY_PYTHON_APP_IMAGE
envFrom:
- configMapRef:
name: my-python-app-consumer-env
- secretRef:
name: my-python-app-consumer-secrets
apiVersion: v1
kind: ConfigMap
metadata:
name: client-realtime-transactional-consumer-env
data:
EVENT_HUB_NAME: $(EVENT_HUB_NAME)
EVENT_HUB_NAMESPACE: $(EVENT_HUB_NAMESPACE)
EVENT_HUB_USER: $(EVENT_HUB_USER)
STORAGE_ACCOUNT_NAME: $(STORAGE_ACCOUNT_NAME)
STORAGE_LEASE_CONTAINER_NAME: $(STORAGE_LEASE_CONTAINER_NAME)
CHECK_MERCHANTS_COLLECTION: $(CHECK_MERCHANTS_COLLECTION)
LOG_LEVEL: $(LOG_LEVEL)
EVENT_HUB_COOLDOWN_IN_SECONDS: $(EVENT_HUB_COOLDOWN_IN_SECONDS)
EVENT_HUB_RELEASE_PUMP_ON_TIMEOUT: $(EVENT_HUB_RELEASE_PUMP_ON_TIMEOUT)
EVENT_HUB_MAX_BATCH_SIZE: $(EVENT_HUB_MAX_BATCH_SIZE)
EVENT_HUB_PREFETCH: $(EVENT_HUB_PREFETCH)
EVENT_HUB_CONSUMER_GROUP: $(EVENT_HUB_CONSUMER_GROUP)
NEW_RELIC_LICENSE_KEY: $(NEW_RELIC_LICENSE_KEY)
NEW_RELIC_MONITOR_MODE: $(NEW_RELIC_MONITOR_MODE)
NEW_RELIC_APP_NAME: $(NEW_RELIC_APP_NAME)
apiVersion: v1
kind: Secret
metadata:
name: client-realtime-transactional-consumer-secrets
type: Opaque
stringData:
EVENT_HUB_KEY: $(EVENT_HUB_KEY)
STORAGE_KEY: $(STORAGE_KEY)
name: "MyPythonApp-CI"
trigger:
- master
- develop
pool:
vmImage: "ubuntu-latest"
variables:
DockerImageName: "$(GoogleProjectID)/my-python-app-consumer-app"
steps:
- task: UsePythonVersion@0
displayName: "Use Python 3.7"
inputs:
versionSpec: "3.7"
architecture: "x64"
- script: pip install pipenv
displayName: "Install pipenv"
- script: pipenv install --system --dev --deploy --ignore-pipfile
displayName: "Install dependencies"
- script: make coverage
displayName: "Run tests"
- script: make lint
displayName: "Run lint"
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFiles: "**/test-*.xml"
testRunTitle: "Publish test results for Python 3.7"
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: "$(System.DefaultWorkingDirectory)/**/coverage.xml"
reportDirectory: "$(System.DefaultWorkingDirectory)/**/htmlcov"
- task: CopyFiles@2
inputs:
sourceFolder: "$(System.DefaultWorkingDirectory)"
contents: "kubernetes/*"
targetFolder: "$(Build.ArtifactStagingDirectory)"
- task: CmdLine@1
displayName: "Lock image version in deployment.yaml and copy file"
inputs:
filename: /bin/bash
arguments: '-c "awk ''{gsub(\"MY_PYTHON_APP_CONSUMER_IMAGE\", \"gcr.io/$(DockerImageName):$(Build.BuildId)\", $0); print}'' kubernetes/deployment.yaml > $(Build.ArtifactStagingDirectory)/kubernetes/deployment.yaml"'
- task: PublishBuildArtifacts@1
displayName: "Publish Artifact"
inputs:
PathtoPublish: "$(Build.ArtifactStagingDirectory)"
- task: Docker@0
displayName: "Build image"
inputs:
containerregistrytype: "Container Registry"
dockerRegistryConnection: "GCR - MyPythonApp-NonPRD"
imageName: "$(DockerImageName):$(Build.BuildId)"
- task: Docker@0
displayName: "Publish image"
inputs:
containerregistrytype: "Container Registry"
dockerRegistryConnection: "GCR - MyPythonApp-NonPRD"
action: "Push an image"
imageName: "$(DockerImageName):$(Build.BuildId)"
UFA!
Entregamos!
Dificuldades
Não estava realtime
O nosso gráfico de escrita no banco estava assim...
Instrumentando o código
No detalhe...
Batch de 100 msgs
Avg de 46.2 msgs inseridas / batch
Avg time de 5000ms por batch
IT'S TEN TIMES FASTER
Gráfico pós-deploy
No NewRelic...
E um dia normal...
slides: https://slides.com/biancarosa__/consumindo-msgs-python-k8s
outras talks: http://biancarosa.com.br/talks/
Temos vagas ;)
Processando milhões de mensagens com Python e Kubernetes
By Bianca Rosa
Processando milhões de mensagens com Python e Kubernetes
- 1,198