SIENTIAPDE-1017

Enhance OPC ingestor with Redis authentication and update Helm chart configurations. Improve README for clarity on Docker and Kubernetes setup.
This commit is contained in:
vitor-aignosi
2025-04-25 13:26:42 -03:00
parent da079c6320
commit 9eb483ee92
15 changed files with 522 additions and 27 deletions

View File

@@ -3,58 +3,58 @@ OPC gateway to manage Scouter pipelines
## Local tests
### Generate your ssh key to Docker
'''
```
ssh-keygen -t ed25519 -C "docker-access" -f ~/.ssh/id_ed25519_docker
'''
```
Add the public key to yout Git SSH keys
### Enable Docker BuildKit
'''
```
export DOCKER_BUILDKIT=1
'''
```
or make it permanent:
'''
```
echo '{ "features": { "buildkit": true } }' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
'''
```
### Run docker compose
'''
```
docker compose down -v
docker compose build --ssh default=$HOME/.ssh/id_ed25519_docker
docker compose up -d
'''
```
### Populate redis server
Create venv with python3.11
'''
```
python3.11 -m venv venv
source ./venv/bin/activate
'''
```
Install requirements
'''
```
pip install -r requirements.txt
'''
```
Run feeder
'''
```
python simulator/redis-feeder.py
'''
```
## Unit tests
### Install pytest
'''
```
pip install pytest
'''
```
### Run pytest
'''
```
pytest
'''
```
### Get current coverage
'''
```
pip install pytest-cov
pytest --cov=ingestor
'''
```
### Generate complete report
'''
```
pytest --cov=ingestor --cov-report=html
'''
```

23
helm/.helmignore Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

26
helm/Chart.yaml Normal file
View File

@@ -0,0 +1,26 @@
apiVersion: v2
name: sientia-module
description: A generic application helm for Kubernetes. Contains a set of templates
that can be used to deploy a generic sientia python module to a Kubernetes cluster,
based in some git repository and a generic docker image.
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

73
helm/README.md Normal file
View File

@@ -0,0 +1,73 @@
# How to create an image and deploy this helm chart:
## Image creation:
### Generate your ssh key to Docker
```
ssh-keygen -t ed25519 -C "docker-access" -f ~/.ssh/id_ed25519_docker
```
Add the public key to yout Git SSH keys in your git platform
### Enable Docker BuildKit
```
export DOCKER_BUILDKIT=1
```
or make it permanent:
```
echo '{ "features": { "buildkit": true } }' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
```
### Build and upload image
```
make -C docker
```
## SSH secrets:
### Create secret to your git ssh credentials
We need a dedicated keypair to access GitHub from a Kubernetes pod without exposing your user credentials or tokens.
```
ssh-keygen -t ed25519 -f git_key -C "k8s-deploy-key" -N ""
```
- ```-t ed25519```: modern, secure key type
- ```-f git_key```: saves to git_key (private) and git_key.pub (public)
- ```-C "..."```: comment to identify this key
- ```-N ""```: no passphrase (for non-interactive use in pods)
### Add the Public Key to GitHub (Read-Only Deploy Key)
Deploy keys allow read-only access to a specific repo. This avoids using user tokens or giving broad permissions.
1. Copy your public key:
```
cat git_key.pub
```
2. Go to your GitHub repo → Settings → Deploy Keys
3. Click “Add deploy key”
- Title: K8s ReadOnly
- Key: paste your git_key.pub
- ✅ Check “Allow read access”
- ❌ Do NOT check “Allow write access”
### Create a Kubernetes Secret from the Private Key
Kubernetes Secrets securely store sensitive data like private keys. This secret will later be mounted into your pod for Git to use.
```
kubectl create secret generic git-ssh-key \
--namespace sientia-opc \
--from-file=ssh-privatekey=git_key \
--type=kubernetes.io/ssh-auth
```
- ```ssh-privatekey```: required key name for type kubernetes.io/ssh-auth
- Secret is base64-encoded and stored in Kubernetes (not encrypted unless you're using an external secret manager or encryption at rest is enabled)

View File

@@ -0,0 +1,3 @@
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "sientia-module.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "sientia-module.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "sientia-module.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "sientia-module.labels" -}}
helm.sh/chart: {{ include "sientia-module.chart" . }}
{{ include "sientia-module.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "sientia-module.selectorLabels" -}}
app.kubernetes.io/name: {{ include "sientia-module.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "sientia-module.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "sientia-module.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,92 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.nameOverride | default (include "sientia-module.fullname" .) }}
labels:
{{- include "sientia-module.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "sientia-module.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sientia-module.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sientia-module.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- if .Values.ssh.enabled }}
- name: ssh-key
mountPath: {{ .Values.ssh.sshPath }}
readOnly: true
- name: ssh-known-hosts
mountPath: {{ .Values.ssh.knownHostsPath }}
readOnly: true
{{- end }}
volumes:
{{- if .Values.ssh.enabled }}
- name: ssh-key
secret:
secretName: {{ .Values.ssh.secretName }}
items:
- key: ssh-privatekey
path: id_ed25519
- name: ssh-known-hosts
configMap:
name: {{ include "sientia-module.fullname" . }}-ssh-known-hosts
items:
- key: known_hosts
path: known_hosts
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

32
helm/templates/hpa.yaml Normal file
View File

@@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "sientia-module.fullname" . }}
labels:
{{- include "sientia-module.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "sientia-module.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "sientia-module.fullname" . }}-ssh-known-hosts
data:
known_hosts: |
{{ .Files.Get "files/github_known_hosts" | indent 4 }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
name: "{{ .Values.nameOverride }}-service"
namespace: "{{ .Values.namespace }}"
spec:
selector:
app: "{{ .Values.nameOverride }}"
ports:
- port: {{ .Values.service.port | int }}
targetPort: {{ .Values.service.targetPort | int }}
type: "{{ .Values.service.type }}"
{{- end }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "sientia-module.serviceAccountName" . }}
labels:
{{- include "sientia-module.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

145
helm/values.yaml Normal file
View File

@@ -0,0 +1,145 @@
# Default values for sientia-module.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: aignosi.azurecr.io/sientia-module
# This sets the pull policy for images.
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
tag: "0.0.1"
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets:
- name: docker-hub-secret
# This is to override the chart name.
nameOverride: "sientia-opc-ingestor"
fullnameOverride: "sientia-opc-ingestor"
namespace: sientia-opc
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: "sientia-opc-ingestor"
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
tcpSocket:
port: 4840
initialDelaySeconds: 150
periodSeconds: 15
readinessProbe:
tcpSocket:
port: 4840
initialDelaySeconds: 120
periodSeconds: 10
# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
service:
enabled: false
type: ClusterIP
port: 4840
targetPort: 4840
env:
# Entrypoint variables
- name: GITHUB_REPO_URL
value: git@github.com:Aignosi/sientia-dataops-opc-ingestor.git
- name: GITHUB_BRANCH
value: SIENTIAPDE-988-criar-ingestor-opc
- name: PYTHON_APP
value: ingestor.app
# Application variables
- name: KAFKA_SERVERS
value: kafka.kafka.svc.cluster.local:9092
- name: REDIS_HOST
value: redis-master.redis.svc.cluster.local
- name: REDIS_PORT
value: "6379"
- name: LEASE_TTL
value: 10
- name: HEARTBEAT_TTL
value: 20
- name: POLL_INTERVAL
value: 5
ssh:
enabled: true
secretName: git-ssh-key-temp
sshPath: /mnt/.ssh
knownHostsPath: /mnt/known_hosts
# kubectl create secret docker-registry docker-hub-secret --namespace sientia-opc --docker-server=http://aignosi.azurecr.io --docker-username=aignosi --docker-password=5I5zpQ6sRaHqX1hD3dr+2mo647yO3FRc359/wu6gsP+ACRDRz5mp
# helm upgrade --install sientia-dataops-opc-simulator ./sientia-module -n sientia-opc --create-namespace

View File

@@ -29,6 +29,8 @@ class Ingestor:
kafka_servers = getenv("KAFKA_SERVERS", "localhost:9092")
self.redis_host = getenv("REDIS_HOST", "localhost")
self.redis_port = int(getenv("REDIS_PORT", 6379))
self.redis_username = getenv("REDIS_USERNAME", None)
self.redis_password = getenv("REDIS_PASSWORD", None)
self.lease_ttl = int(getenv("LEASE_TTL", 10))
self.heartbeat_ttl = int(getenv("HEARTBEAT_TTL", 20))
self.pod_id = getenv("HOSTNAME", "localhost")
@@ -103,7 +105,7 @@ class Ingestor:
self.ingestor_manager = IngestorManager(
self.kafka_servers, self.redis_host, self.redis_port, self.lease_ttl,
self.heartbeat_ttl, self.pod_id, self.poll_interval, self.logger
self.heartbeat_ttl, self.pod_id, self.poll_interval, self.logger, self.redis_username, self.redis_password
)
# Declare ingestor ative

View File

@@ -11,12 +11,13 @@ class IngestorManager():
def __init__(self,
kafka_servers: str, redis_host: str, redis_port: int,
lease_ttl: int, heartbeat_ttl: int, pod_id: str,
poll_interval: int, logger: Logger):
poll_interval: int, logger: Logger,
redis_username: str = None, redis_password: str = None):
self.data_manager = DataManager(kafka_servers, logger)
self.opc_managers = {}
self.resource_manager = ResourceManager(
redis_host, redis_port, lease_ttl, heartbeat_ttl, pod_id
redis_host, redis_port, lease_ttl, heartbeat_ttl, pod_id, redis_username, redis_password
)
self.number_of_slots = 0
self.poll_interval = poll_interval

View File

@@ -5,8 +5,10 @@ from redis import Redis
class ResourceManager:
def __init__(self, host: str, port: int,
lease_ttl: int, heartbeat_ttl: int, pod_id: str) -> None:
self.redis = Redis(host=host, port=port, decode_responses=True)
lease_ttl: int, heartbeat_ttl: int, pod_id: str,
username: str = None, password: str = None) -> None:
self.redis = Redis(host=host, port=port, decode_responses=True,
username=username, password=password)
self.lease_ttl = lease_ttl
self.heartbeat_ttl = heartbeat_ttl
self.pod_id = pod_id