├── .gitignore ├── README.azure.md ├── README.eks.md ├── README.gce.md ├── README.kops.md ├── README.md ├── README.minikube.md ├── aks-reduced ├── kustomization.yaml └── patches │ ├── cassandra.reaper.yaml │ ├── cassandra.yaml │ ├── common-settings.yaml │ ├── elasticsearch.data.yaml │ ├── elasticsearch.master.yaml │ ├── external-access.yaml │ ├── grafana.yaml │ ├── kafka.yaml │ ├── opennms.alec.yaml │ ├── opennms.core.yaml │ ├── opennms.minion.yaml │ ├── opennms.nephron.yaml │ ├── opennms.sentinel.yaml │ ├── opennms.ui.yaml │ ├── postgresql.yaml │ └── zookeeper.yaml ├── aks ├── kustomization.yaml └── patches │ ├── common-settings.yaml │ └── external-access.yaml ├── diagram.png ├── docker-images ├── minion-gns3 │ ├── Dockerfile │ ├── README.md │ ├── build.sh │ ├── entrypoint.sh │ └── etc-overlay │ │ ├── featuresBoot.d │ │ └── grpc.boot │ │ ├── org.opennms.core.ipc.grpc.client.cfg │ │ ├── org.opennms.features.telemetry.listeners-udp-4729.cfg │ │ ├── org.opennms.features.telemetry.listeners-udp-4738.cfg │ │ ├── org.opennms.features.telemetry.listeners-udp-50001.cfg │ │ ├── org.opennms.features.telemetry.listeners-udp-6343.cfg │ │ ├── org.opennms.features.telemetry.listeners-udp-8877.cfg │ │ ├── org.opennms.minion.controller.cfg │ │ ├── org.opennms.netmgt.syslog.cfg │ │ └── org.opennms.netmgt.trapd.cfg └── nephron │ ├── Dockerfile │ └── README.md ├── gce-reduced ├── kustomization.yaml └── patches │ ├── common-settings.yaml │ └── external-access.yaml ├── gce ├── kustomization.yaml └── patches │ ├── common-settings.yaml │ └── external-access.yaml ├── manifests ├── cassandra.reaper.yaml ├── cassandra.yaml ├── cert-manager.yaml ├── config │ ├── create-topics.sh │ ├── grafana-init.sh │ ├── onms-alec-init.sh │ ├── onms-core-init.sh │ ├── onms-helm-init.sh │ ├── onms-minion-init.sh │ ├── onms-nephron-init.sh │ ├── onms-sentinel-init.sh │ └── onms-ui-init.sh ├── elastic-hq.yaml ├── elasticsearch.curator.yaml ├── elasticsearch.data.yaml ├── elasticsearch.master.yaml ├── event-watcher.yaml ├── external-access.yaml ├── flink.job-manager.yaml ├── flink.task-manager.yaml ├── grafana.helm.yaml ├── grafana.renderer.yaml ├── grafana.yaml ├── grpc-server.yaml ├── hasura.yaml ├── jaeger.yaml ├── kafka.converter.yaml ├── kafka.manager.yaml ├── kafka.producer.enhancer.yaml ├── kafka.topics.yaml ├── kafka.yaml ├── kibana.yaml ├── kustomization.yaml ├── namespace.yaml ├── opennms.alec.yaml ├── opennms.core.yaml ├── opennms.minion.yaml ├── opennms.nephron.yaml ├── opennms.sentinel.yaml ├── opennms.ui.yaml ├── postgresql.yaml └── zookeeper.yaml ├── minikube ├── kustomization.yaml └── patches │ ├── common-settings.yaml │ ├── external-access.yaml │ └── opennms.core.yaml ├── minion.yaml ├── reduced ├── kustomization.yaml └── patches │ ├── cassandra.reaper.yaml │ ├── cassandra.yaml │ ├── elastic-hq.yaml │ ├── elasticsearch.data.yaml │ ├── elasticsearch.master.yaml │ ├── grafana.yaml │ ├── grpc-server.yaml │ ├── hasura.yaml │ ├── kafka.yaml │ ├── kibana.yaml │ ├── opennms.alec.yaml │ ├── opennms.core.yaml │ ├── opennms.minion.yaml │ ├── opennms.nephron.yaml │ ├── opennms.sentinel.yaml │ ├── opennms.ui.yaml │ ├── postgresql.yaml │ └── zookeeper.yaml ├── serverless ├── .gitignore ├── README.fission.md ├── README.knative.md ├── README.kubeless.md ├── README.md ├── fission-mqtrigger-kafka.yaml ├── knative-kafka-source.yaml ├── knative-service.yaml ├── kubeless-mqtrigger-kafka.yaml ├── setup-istio-knative.sh ├── slack-forwarder-go │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── main_test.go └── slack-forwarder │ ├── .gitignore │ ├── __mocks__ │ └── axios.js │ ├── alarm2slack.js │ ├── alarm2slack.test.js │ ├── package.json │ ├── pod2slack.js │ └── pod2slack.test.js └── tools ├── event-watcher ├── .gitignore ├── Dockerfile ├── README.md ├── event-watcher.go ├── go.mod └── go.sum ├── k8s-to-onms ├── .gitignore ├── README.md ├── go.mod ├── go.sum └── main.go └── kafka-converter ├── .gitignore ├── Dockerfile ├── README.md ├── api ├── build.sh ├── collectionset.proto ├── opennms-kafka-producer.proto └── producer │ ├── collectionset.pb.go │ └── opennms-kafka-producer.pb.go ├── docker-entrypoint.sh ├── go.mod ├── go.sum ├── main.go └── mock └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | experiments/ 3 | temp/ 4 | TODO 5 | *.pem 6 | *.jks 7 | *.proto 8 | -------------------------------------------------------------------------------- /README.minikube.md: -------------------------------------------------------------------------------- 1 | # Setup Cluster with Minikube 2 | 3 | For testing purposes, it would be nice to be able to start a reduced version of this lab through `minikube`. 4 | 5 | For this reason, we use `kustomize` to generate a reduced version of the templates through `kubectl`. 6 | 7 | ## Requirements 8 | 9 | * Install the [minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) binary on your machine; version 1.22.x or newer recommended. 10 | 11 | > **WARNING:** Please note that all the manifests were verified for Kubernetes 1.20. If you're going to use a newer version, please adjust the API versions of the manifests. In particular, `batch/v1beta1` for `CrobJobs` in [elasticsearch.curator.yaml](manifests/elasticsearch.curator.yaml), and `policy/v1beta1` for `PodDisruptionBudget` in [zookeeper.yaml](manifests/zookeeper.yaml). Similarly, if you're planing to use a version is older than 1.20, make sure to do the same for `networking.k8s.io/v1` in [external-access.yaml](manifests/external-access.yaml). 12 | 13 | ## Cluster Configuration 14 | 15 | Start minikube with the following recommended settings: 16 | 17 | ```bash 18 | minikube start --cpus=8 --memory=32g --disk-size=60g \ 19 | --cni=calico \ 20 | --container-runtime=containerd \ 21 | --addons=ingress \ 22 | --addons=ingress-dns \ 23 | --addons=metrics-server 24 | ``` 25 | 26 | > **IMPORTANT**: on macOS, it is better to use Hyperkit rather than VirtualBox, as I found it a lot faster to work with. You can enforce it by passing `--driver hyperkit`. 27 | 28 | Depending on the version you're running, you might encounter problems when creating ingress resources due to admission control validations. The following is a workaround you could use: 29 | 30 | ```bash 31 | kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission 32 | ``` 33 | 34 | ## Install the CertManager 35 | 36 | The [cert-manager](https://cert-manager.readthedocs.io/en/latest/) add-on is required in order to provide HTTP/TLS support through [LetsEncrypt](https://letsencrypt.org) to the HTTP services managed by the ingress controller. Although, for `minikube`, a self-signed certificate will be used. 37 | 38 | ```bash 39 | CMVER=$(curl -s https://api.github.com/repositories/92313258/releases/latest | grep tag_name | cut -d '"' -f 4) 40 | kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/$CMVER/cert-manager.yaml 41 | ``` 42 | 43 | > **NOTE**: For more details, check the [installation guide](http://docs.cert-manager.io/en/latest/getting-started/install.html). 44 | 45 | ## Install Jaeger Operator 46 | 47 | ```bash 48 | JAEGERVER=$(curl -s https://api.github.com/repos/jaegertracing/jaeger-operator/releases/latest | grep tag_name | cut -d '"' -f 4) 49 | kubectl create ns observability 50 | kubectl apply -n observability \ 51 | -f https://github.com/jaegertracing/jaeger-operator/releases/download/$JAEGERVER/jaeger-operator.yaml 52 | ``` 53 | 54 | ## Manifets 55 | 56 | Execute the following to apply a reduced version of the original YAML files located at the [manifests](manifests) directory, that fits the suggested settings. 57 | 58 | ```bash 59 | kubectl apply -k minikube 60 | ``` 61 | 62 | It could about 15 minutes to have all the components up and running compared to cloud-based solutions (as we have one node, despite the resource reduction), which is why I encourage you to use a cloud-based solution or a bare-metal Kubernetes cluster. 63 | 64 | ## DNS 65 | 66 | Please take a look at the documentation of [ingress-dns](https://github.com/kubernetes/minikube/tree/master/deploy/addons/ingress-dns) for more information about how to use it, to avoid messing with `/etc/hosts`. 67 | 68 | For instance, for macOS: 69 | 70 | ```bash 71 | cat < **WARNING**: Keep in mind that the certificates used here are self-signed. 80 | 81 | # Start Minion 82 | 83 | From the directory on which you checked out this repository, do the following: 84 | 85 | ```bash 86 | sed 's/aws.agalue.net/test/' minion.yaml > minion-minikube.yaml 87 | 88 | kubectl get secret minion-cert -n opennms -o json | jq -r '.data["tls.crt"]' | base64 --decode > client.pem 89 | kubectl get secret minion-cert -n opennms -o json | jq -r '.data["tls.key"]' | base64 --decode > client-key.pem 90 | openssl pkcs8 -topk8 -nocrypt -in client-key.pem -out client-pkcs8_key.pem 91 | 92 | kubectl get secret onms-ca -n opennms -o json | jq -r '.data["tls.crt"]' | base64 --decode > onms-ca.pem 93 | keytool -importcert -alias onms-ca -file onms-ca.pem -storepass 0p3nNM5 -keystore onms-ca-trust.jks -noprompt 94 | 95 | docker run --name minion \ 96 | -e OPENNMS_HTTP_USER=admin \ 97 | -e OPENNMS_HTTP_PASS=admin \ 98 | -e JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/minion/etc/onms-ca-trust.jks -Djavax.net.ssl.trustStorePassword=0p3nNM5" \ 99 | -p 8201:8201 \ 100 | -p 1514:1514/udp \ 101 | -p 1162:1162/udp \ 102 | -p 8877:8877/udp \ 103 | -p 11019:11019 \ 104 | -v $(pwd)/client.pem:/opt/minion/etc/client.pem \ 105 | -v $(pwd)/client-pkcs8_key.pem:/opt/minion/etc/client-key.pem \ 106 | -v $(pwd)/onms-ca-trust.jks:/opt/minion/onms-ca-trust.jks \ 107 | -v $(pwd)/minion-minikube.yaml:/opt/minion/minion-config.yaml \ 108 | opennms/minion:28.1.1 -c 109 | ``` 110 | 111 | > **IMPORTANT**: Make sure to use the same version as OpenNMS. The above requires using a custom content for the `INSTANCE_ID` (see [minion.yaml](minion.yaml)). Make sure it matches the content of [kustomization.yaml](manifests/kustomization.yaml). 112 | 113 | # Cleanup 114 | 115 | ```bash 116 | sudo rm -f /etc/resolver/minikube-default-test 117 | minikube delete 118 | rm -f *.pem *.jks minion-minikube.yaml 119 | ``` 120 | -------------------------------------------------------------------------------- /aks-reduced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | bases: 5 | - ../manifests 6 | 7 | patchesStrategicMerge: 8 | - patches/common-settings.yaml 9 | - patches/cassandra.reaper.yaml 10 | - patches/cassandra.yaml 11 | - patches/elasticsearch.data.yaml 12 | - patches/elasticsearch.master.yaml 13 | - patches/grafana.yaml 14 | - patches/kafka.yaml 15 | - patches/opennms.core.yaml 16 | - patches/opennms.minion.yaml 17 | - patches/opennms.nephron.yaml 18 | - patches/opennms.sentinel.yaml 19 | - patches/opennms.ui.yaml 20 | - patches/postgresql.yaml 21 | - patches/zookeeper.yaml 22 | - patches/external-access.yaml 23 | -------------------------------------------------------------------------------- /aks-reduced/patches/cassandra.reaper.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: cassandra-reaper 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: cassandra-reaper 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /aks-reduced/patches/cassandra.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: cassandra 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: cassandra 14 | resources: 15 | limits: 16 | memory: 2Gi 17 | requests: 18 | memory: 1Gi 19 | $patch: replace 20 | -------------------------------------------------------------------------------- /aks-reduced/patches/common-settings.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: common-settings 8 | namespace: opennms 9 | data: 10 | DOMAIN: azure.agalue.net 11 | TIMEZONE: America/New_York 12 | OPENNMS_INSTANCE_ID: K8S 13 | CASSANDRA_CLUSTER_NAME: OpenNMS 14 | CASSANDRA_DC: Main 15 | CASSANDRA_REPLICATION_FACTOR: "2" 16 | ELASTIC_INDEX_STRATEGY_FLOWS: daily 17 | ELASTIC_REPLICATION_FACTOR: "2" 18 | ELASTIC_NUM_SHARDS: "6" 19 | KAFKA_NUM_PARTITIONS: "6" 20 | KAFKA_REPLICATION_FACTOR: "2" 21 | MINION_LOCATION: Kubernetes 22 | -------------------------------------------------------------------------------- /aks-reduced/patches/elasticsearch.data.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: esdata 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: esdata 14 | resources: 15 | limits: 16 | memory: 2Gi 17 | requests: 18 | memory: 1Gi 19 | $patch: replace 20 | -------------------------------------------------------------------------------- /aks-reduced/patches/elasticsearch.master.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: esmaster 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | containers: 14 | - name: esmaster 15 | env: 16 | - name: xpack.security.enabled 17 | $patch: delete 18 | - name: discovery.seed_hosts 19 | value: esmaster.opennms.svc.cluster.local 20 | - name: cluster.initial_master_nodes 21 | value: esmaster-0 22 | - name: bootstrap.memory_lock 23 | value: 'false' 24 | resources: 25 | limits: 26 | memory: 2Gi 27 | requests: 28 | memory: 1Gi 29 | $patch: replace 30 | -------------------------------------------------------------------------------- /aks-reduced/patches/external-access.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: networking.k8s.io/v1 5 | kind: Ingress 6 | metadata: 7 | name: onms-ingress 8 | namespace: opennms 9 | spec: 10 | tls: 11 | - secretName: opennms-ingress-cert 12 | hosts: 13 | - onms.azure.agalue.net 14 | - grafana.azure.agalue.net 15 | - hasura.azure.agalue.net 16 | - kafka-manager.azure.agalue.net 17 | - kibana.azure.agalue.net 18 | - elastichq.azure.agalue.net 19 | - tracing.azure.agalue.net 20 | rules: 21 | - host: onms.azure.agalue.net 22 | http: 23 | paths: 24 | - path: / 25 | pathType: Prefix 26 | backend: 27 | service: 28 | name: opennms-core 29 | port: 30 | number: 8980 31 | - host: grafana.azure.agalue.net 32 | http: 33 | paths: 34 | - path: / 35 | pathType: Prefix 36 | backend: 37 | service: 38 | name: grafana 39 | port: 40 | number: 3000 41 | - host: hasura.azure.agalue.net 42 | http: 43 | paths: 44 | - path: / 45 | pathType: Prefix 46 | backend: 47 | service: 48 | name: hasura 49 | port: 50 | number: 8080 51 | - host: kafka-manager.azure.agalue.net 52 | http: 53 | paths: 54 | - path: / 55 | pathType: Prefix 56 | backend: 57 | service: 58 | name: kafka-manager 59 | port: 60 | number: 9000 61 | - host: kibana.azure.agalue.net 62 | http: 63 | paths: 64 | - path: / 65 | pathType: Prefix 66 | backend: 67 | service: 68 | name: kibana 69 | port: 70 | number: 5601 71 | - host: elastichq.azure.agalue.net 72 | http: 73 | paths: 74 | - path: / 75 | pathType: Prefix 76 | backend: 77 | service: 78 | name: elastichq 79 | port: 80 | number: 5000 81 | - host: tracing.azure.agalue.net 82 | http: 83 | paths: 84 | - path: / 85 | pathType: Prefix 86 | backend: 87 | service: 88 | name: onms-tracing-query 89 | port: 90 | number: 16686 91 | 92 | --- 93 | apiVersion: networking.k8s.io/v1 94 | kind: Ingress 95 | metadata: 96 | name: grpc-ingress 97 | namespace: opennms 98 | spec: 99 | tls: 100 | - secretName: grpc-ingress-cert 101 | hosts: 102 | - grpc.azure.agalue.net 103 | rules: 104 | - host: grpc.azure.agalue.net 105 | http: 106 | paths: 107 | - path: / 108 | pathType: Prefix 109 | backend: 110 | service: 111 | name: grpc-server 112 | port: 113 | number: 8990 114 | -------------------------------------------------------------------------------- /aks-reduced/patches/grafana.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: grafana 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | -------------------------------------------------------------------------------- /aks-reduced/patches/kafka.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: kafka 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: kafka 14 | resources: 15 | limits: 16 | memory: 1Gi 17 | requests: 18 | memory: 512Mi 19 | $patch: replace 20 | 21 | 22 | -------------------------------------------------------------------------------- /aks-reduced/patches/opennms.alec.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: alec 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | affinity: 13 | $patch: delete 14 | containers: 15 | - name: alec 16 | resources: 17 | limits: 18 | memory: 1Gi 19 | requests: 20 | memory: 512Mi 21 | $patch: replace 22 | -------------------------------------------------------------------------------- /aks-reduced/patches/opennms.core.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: onms 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: onms 14 | resources: 15 | limits: 16 | memory: 3Gi 17 | requests: 18 | memory: 2Gi 19 | $patch: replace 20 | -------------------------------------------------------------------------------- /aks-reduced/patches/opennms.minion.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: minion 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: minion 14 | resources: 15 | limits: 16 | memory: 1Gi 17 | requests: 18 | memory: 512Mi 19 | $patch: replace 20 | -------------------------------------------------------------------------------- /aks-reduced/patches/opennms.nephron.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: flink-jobmanager 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: StatefulSet 15 | metadata: 16 | name: flink-jobmanager 17 | namespace: opennms 18 | 19 | --- 20 | $patch: delete 21 | apiVersion: apps/v1 22 | kind: StatefulSet 23 | metadata: 24 | name: flink-tm 25 | namespace: opennms 26 | 27 | --- 28 | $patch: delete 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | metadata: 32 | name: nephron 33 | namespace: opennms 34 | -------------------------------------------------------------------------------- /aks-reduced/patches/opennms.sentinel.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: sentinel 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | affinity: 13 | $patch: delete 14 | containers: 15 | - name: sentinel 16 | resources: 17 | limits: 18 | memory: 1Gi 19 | requests: 20 | memory: 512Mi 21 | $patch: replace 22 | -------------------------------------------------------------------------------- /aks-reduced/patches/opennms.ui.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: opennms-ui 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: onms-ui 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /aks-reduced/patches/postgresql.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: postgres 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: postgres 14 | args: 15 | - postgres 16 | volumeMounts: 17 | - mountPath: /etc/postgresql.conf 18 | $patch: delete 19 | resources: 20 | limits: 21 | memory: 512Mi 22 | requests: 23 | memory: 256Mi 24 | $patch: replace 25 | volumes: 26 | - name: postgresql-config 27 | $patch: delete 28 | -------------------------------------------------------------------------------- /aks-reduced/patches/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: zk 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: zk 14 | resources: 15 | limits: 16 | memory: 512Mi 17 | requests: 18 | memory: 256Mi 19 | $patch: replace 20 | -------------------------------------------------------------------------------- /aks/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | bases: 5 | - ../manifests 6 | 7 | patchesStrategicMerge: 8 | - patches/common-settings.yaml 9 | - patches/external-access.yaml 10 | -------------------------------------------------------------------------------- /aks/patches/common-settings.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: common-settings 8 | namespace: opennms 9 | data: 10 | DOMAIN: azure.agalue.net 11 | TIMEZONE: America/New_York 12 | OPENNMS_INSTANCE_ID: K8S 13 | CASSANDRA_CLUSTER_NAME: OpenNMS 14 | CASSANDRA_DC: Main 15 | CASSANDRA_REPLICATION_FACTOR: "2" 16 | ELASTIC_INDEX_STRATEGY_FLOWS: daily 17 | ELASTIC_REPLICATION_FACTOR: "2" 18 | ELASTIC_NUM_SHARDS: "6" 19 | KAFKA_NUM_PARTITIONS: "6" 20 | KAFKA_REPLICATION_FACTOR: "2" 21 | MINION_LOCATION: Kubernetes 22 | -------------------------------------------------------------------------------- /aks/patches/external-access.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: networking.k8s.io/v1 5 | kind: Ingress 6 | metadata: 7 | name: onms-ingress 8 | namespace: opennms 9 | spec: 10 | tls: 11 | - secretName: opennms-ingress-cert 12 | hosts: 13 | - onms.azure.agalue.net 14 | - onmsui.azure.agalue.net 15 | - cassandra-reaper.azure.agalue.net 16 | - grafana.azure.agalue.net 17 | - hasura.azure.agalue.net 18 | - kafka-manager.azure.agalue.net 19 | - kibana.azure.agalue.net 20 | - elastichq.azure.agalue.net 21 | - tracing.azure.agalue.net 22 | - flink.azure.agalue.net 23 | rules: 24 | - host: onms.azure.agalue.net 25 | http: 26 | paths: 27 | - path: / 28 | pathType: Prefix 29 | backend: 30 | service: 31 | name: opennms-core 32 | port: 33 | number: 8980 34 | - host: onmsui.azure.agalue.net 35 | http: 36 | paths: 37 | - path: / 38 | pathType: Prefix 39 | backend: 40 | service: 41 | name: opennms-ui 42 | port: 43 | number: 8980 44 | - path: /opennms/nrt 45 | pathType: Prefix 46 | backend: 47 | service: 48 | name: opennms-core 49 | port: 50 | number: 8980 51 | - host: cassandra-reaper.azure.agalue.net 52 | http: 53 | paths: 54 | - path: / 55 | pathType: Prefix 56 | backend: 57 | service: 58 | name: cassandra-reaper 59 | port: 60 | number: 8080 61 | - host: grafana.azure.agalue.net 62 | http: 63 | paths: 64 | - path: / 65 | pathType: Prefix 66 | backend: 67 | service: 68 | name: grafana 69 | port: 70 | number: 3000 71 | - host: hasura.azure.agalue.net 72 | http: 73 | paths: 74 | - path: / 75 | pathType: Prefix 76 | backend: 77 | service: 78 | name: hasura 79 | port: 80 | number: 8080 81 | - host: kafka-manager.azure.agalue.net 82 | http: 83 | paths: 84 | - path: / 85 | pathType: Prefix 86 | backend: 87 | service: 88 | name: kafka-manager 89 | port: 90 | number: 9000 91 | - host: kibana.azure.agalue.net 92 | http: 93 | paths: 94 | - path: / 95 | pathType: Prefix 96 | backend: 97 | service: 98 | name: kibana 99 | port: 100 | number: 5601 101 | - host: elastichq.azure.agalue.net 102 | http: 103 | paths: 104 | - path: / 105 | pathType: Prefix 106 | backend: 107 | service: 108 | name: elastichq 109 | port: 110 | number: 5000 111 | - host: tracing.azure.agalue.net 112 | http: 113 | paths: 114 | - path: / 115 | pathType: Prefix 116 | backend: 117 | service: 118 | name: onms-tracing-query 119 | port: 120 | number: 16686 121 | - host: flink.azure.agalue.net 122 | http: 123 | paths: 124 | - path: / 125 | pathType: Prefix 126 | backend: 127 | service: 128 | name: flink-jobmanager 129 | port: 130 | number: 8081 131 | 132 | --- 133 | apiVersion: networking.k8s.io/v1 134 | kind: Ingress 135 | metadata: 136 | name: grpc-ingress 137 | namespace: opennms 138 | spec: 139 | tls: 140 | - secretName: grpc-ingress-cert 141 | hosts: 142 | - grpc.azure.agalue.net 143 | rules: 144 | - host: grpc.azure.agalue.net 145 | http: 146 | paths: 147 | - path: / 148 | pathType: Prefix 149 | backend: 150 | service: 151 | name: grpc-server 152 | port: 153 | number: 8990 154 | -------------------------------------------------------------------------------- /diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agalue/opennms-drift-kubernetes/36e0c29d8904214b6a2b7a88d1ff5c5e1d426bcd/diagram.png -------------------------------------------------------------------------------- /docker-images/minion-gns3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV MINION_SOURCE=stable 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | RUN apt-get update -y && \ 7 | apt-get upgrade -y && \ 8 | apt-get install snmp snmp-mibs-downloader tzdata iproute2 iputils-ping curl rsync gnupg ca-certificates ssh sshpass openjdk-11-jre -y && \ 9 | echo "deb https://debian.opennms.org $MINION_SOURCE main" | tee /etc/apt/sources.list.d/opennms.list && \ 10 | curl https://debian.opennms.org/OPENNMS-GPG-KEY 2>/dev/null | apt-key add - && \ 11 | apt-get update && \ 12 | apt-get install opennms-minion -y && \ 13 | apt-get clean && \ 14 | ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime && \ 15 | dpkg-reconfigure --frontend noninteractive tzdata 16 | 17 | USER root 18 | 19 | COPY ./etc-overlay /usr/share/minion/etc-overlay 20 | COPY ./entrypoint.sh / 21 | 22 | LABEL maintainer "Alejandro Galue " \ 23 | license="AGPLv3" \ 24 | name="Minion for GNS3" 25 | 26 | ENTRYPOINT [ "/entrypoint.sh" ] 27 | 28 | EXPOSE 8201/tcp 162/udp 514/udp 50001/udp 50002/udp 8877/udp 4729/udp 6343/udp 4738/udp 29 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/README.md: -------------------------------------------------------------------------------- 1 | # Minion image based on Ubuntu 18 for GNS3 2 | 3 | This is a special version of the Minion image that runs as root on Ubuntu, in order to allow it to run within GNS3, be able to pass NIC information (to connect it to virtual devices within GNS3), and pass all the required fields via environment variables, as passing files is not an option, and the solution uses gRPC; which is why the official minion-gns3 image cannot be used. 4 | 5 | Environment Variables: 6 | 7 | * `GRPC_SRV` gRPC server FQDN or IP Address (defaults to `grpc.aws.agalue.net`) 8 | * `GRPC_PORT` gRPC server port (defaults to `443`) 9 | * `GRPC_TLS` with `true` to enable TLS or `false` to disable it (defaults to `true`) 10 | * `ONMS_URL` The OpenNMS Base URL (defaults to `https://onms.aws.agalue.net/opennms`) 11 | * `LOCATION` The Minion Location (defaults to `GNS3`) 12 | 13 | Enabled Features: 14 | 15 | * NetFlow 5 listener on port 8877 16 | * NetFlow 9 listener on port 4729 17 | * IPFIX listener on port 4738 18 | * SFlow listener on port 6343 19 | * NX-OS (Telemetry) listener on port 50001 20 | * Syslog listener on port 514 21 | * SNMP Trap listener on port 162 22 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TAG=${1-latest} 4 | REPO=${2-stable} 5 | 6 | docker build -t agalue/minion-gns3:$TAG --build-arg MINION_SOURCE=$REPO . 7 | docker push agalue/minion-gns3:$TAG 8 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Cause false/positives 4 | # shellcheck disable=SC2086 5 | 6 | MINION_HOME="/usr/share/minion" 7 | MINION_OVERLAY_ETC="$MINION_HOME/etc-overlay" 8 | MINION_CFG=${MINION_HOME}/etc/org.opennms.minion.controller.cfg 9 | 10 | LOCATION=${LOCATION-GNS3} 11 | ONMS_URL=${ONMS_URL-https://onms.aws.agalue.net/opennms} 12 | GRPC_SRV=${GRPC_SRV-grpc.aws.agalue.net} 13 | GRPC_PORT=${GRPC_PORT-443} 14 | GRPC_TLS=${GRPC_TLS-true} 15 | 16 | # Error codes 17 | E_ILLEGAL_ARGS=126 18 | 19 | # Overlay etc specific config 20 | if [ -d "${MINION_OVERLAY_ETC}" ] && [ -n "$(ls -A ${MINION_OVERLAY_ETC})" ]; then 21 | echo "Apply custom etc configuration from ${MINION_OVERLAY_ETC}." 22 | rsync -avr ${MINION_OVERLAY_ETC}/ ${MINION_HOME}/etc/ 23 | sed -r -i "/^id/s/=.*/=${HOSTNAME}/" ${MINION_CFG} 24 | sed -r -i "s|_ONMS_URL_|${ONMS_URL}|" ${MINION_CFG} 25 | sed -r -i "s|_LOCATION_|${LOCATION}|" ${MINION_CFG} 26 | sed -r -i "s|_GRPC_SRV_|${GRPC_SRV}|" ${MINION_HOME}/etc/org.opennms.core.ipc.grpc.client.cfg 27 | sed -r -i "s|_GRPC_PORT_|${GRPC_PORT}|" ${MINION_HOME}/etc/org.opennms.core.ipc.grpc.client.cfg 28 | sed -r -i "s|_GRPC_TLS_|${GRPC_TLS}|" ${MINION_HOME}/etc/org.opennms.core.ipc.grpc.client.cfg 29 | else 30 | echo "No custom config found in ${MINION_OVERLAY_ETC}. Use default configuration." 31 | fi 32 | 33 | exec ${MINION_HOME}/bin/karaf server 34 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/featuresBoot.d/grpc.boot: -------------------------------------------------------------------------------- 1 | !minion-jms 2 | !opennms-core-ipc-rpc-jms 3 | !opennms-core-ipc-sink-camel 4 | opennms-core-ipc-grpc-client 5 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.core.ipc.grpc.client.cfg: -------------------------------------------------------------------------------- 1 | tls-enabled=_GRPC_TLS_ 2 | host=_GRPC_SRV_ 3 | port=_GRPC_PORT_ 4 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.features.telemetry.listeners-udp-4729.cfg: -------------------------------------------------------------------------------- 1 | name=Netflow-9-Listener 2 | class-name=org.opennms.netmgt.telemetry.listeners.UdpListener 3 | parameters.host=0.0.0.0 4 | parameters.port=4729 5 | parameters.maxPacketSize=16192 6 | parsers.0.name=Netflow-9 7 | parsers.0.class-name=org.opennms.netmgt.telemetry.protocols.netflow.parser.Netflow9UdpParser 8 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.features.telemetry.listeners-udp-4738.cfg: -------------------------------------------------------------------------------- 1 | name=IPFIX-Listener 2 | class-name=org.opennms.netmgt.telemetry.listeners.UdpListener 3 | parameters.host=0.0.0.0 4 | parameters.port=4738 5 | parameters.maxPacketSize=16192 6 | parsers.0.name=IPFIX 7 | parsers.0.class-name=org.opennms.netmgt.telemetry.protocols.netflow.parser.IpfixUdpParser 8 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.features.telemetry.listeners-udp-50001.cfg: -------------------------------------------------------------------------------- 1 | name=NXOS-Listener 2 | class-name=org.opennms.netmgt.telemetry.listeners.UdpListener 3 | parameters.host=0.0.0.0 4 | parameters.port=50001 5 | parameters.maxPacketSize=32768 6 | parsers.0.name=NXOS 7 | parsers.0.class-name=org.opennms.netmgt.telemetry.protocols.common.parser.ForwardParser 8 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.features.telemetry.listeners-udp-6343.cfg: -------------------------------------------------------------------------------- 1 | name=SFlow-Listener 2 | class-name=org.opennms.netmgt.telemetry.listeners.UdpListener 3 | parameters.host=0.0.0.0 4 | parameters.port=6343 5 | parameters.maxPacketSize=16192 6 | parsers.0.name=SFlow 7 | parsers.0.class-name=org.opennms.netmgt.telemetry.protocols.sflow.parser.SFlowUdpParser 8 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.features.telemetry.listeners-udp-8877.cfg: -------------------------------------------------------------------------------- 1 | name=Netflow-5-Listener 2 | class-name=org.opennms.netmgt.telemetry.listeners.UdpListener 3 | parameters.host=0.0.0.0 4 | parameters.port=8877 5 | parameters.maxPacketSize=16192 6 | parsers.0.name=Netflow-5 7 | parsers.0.class-name=org.opennms.netmgt.telemetry.protocols.netflow.parser.Netflow5UdpParser 8 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.minion.controller.cfg: -------------------------------------------------------------------------------- 1 | location=_LOCATION_ 2 | id=gns3-minion 3 | http-url=_ONMS_URL_ 4 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.netmgt.syslog.cfg: -------------------------------------------------------------------------------- 1 | syslog.listen.interface=0.0.0.0 2 | syslog.listen.port=514 3 | # To control how many syslog messages are included in a single package sent to Kafka 4 | syslog.batch.size=5 5 | # To limit how many syslog messages are kept in memory if Kafka is unreachable 6 | syslog.queue.size=1000 7 | -------------------------------------------------------------------------------- /docker-images/minion-gns3/etc-overlay/org.opennms.netmgt.trapd.cfg: -------------------------------------------------------------------------------- 1 | trapd.listen.interface=0.0.0.0 2 | trapd.listen.port=162 3 | # To control how many traps are included in a single message sent to Kafka 4 | trapd.batch.size=5 5 | # To limit how many messages are kept in memory if Kafka is unreachable 6 | trapd.queue.size=1000 7 | -------------------------------------------------------------------------------- /docker-images/nephron/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3-openjdk-11 AS builder 2 | 3 | ENV NEPHRON_VERSION=v0.3.0 4 | 5 | WORKDIR /app 6 | 7 | RUN git clone https://github.com/OpenNMS/nephron.git && \ 8 | cd nephron && \ 9 | git checkout -b ${NEPHRON_VERSION} ${NEPHRON_VERSION} && \ 10 | git submodule init && \ 11 | git submodule update && \ 12 | mvn package -DskipTests 13 | 14 | FROM apache/flink:1.13-java11 15 | 16 | RUN mkdir /data 17 | 18 | COPY --from=builder /app/nephron/assemblies/flink/target/nephron-flink-bundled-*.jar /data/nephron-flink-bundled.jar 19 | -------------------------------------------------------------------------------- /docker-images/nephron/README.md: -------------------------------------------------------------------------------- 1 | # Nephron 2 | 3 | As there are no bundled JARs released for Nephron, this will build a Flink image with a given version of Nephron compiled in the `/data` directory. 4 | 5 | To build the image for version `v0.3.0` (which should be a valid `tag` of the Nephron GitHub repository), do the following: 6 | 7 | ```bash 8 | docker build -t agalue/nephron:0.3.0 --build-arg NEPHRON_VERSION=v0.3.0 . 9 | docker push agalue/nephron:0.3.0 10 | ``` 11 | 12 | > **NOTE:** If you plan to use a different identifier, make sure to update the YAML manifest for Kubernetes. 13 | -------------------------------------------------------------------------------- /gce-reduced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # Warning: uses a single instance per cluster. 4 | 5 | --- 6 | bases: 7 | - ../reduced 8 | 9 | patchesStrategicMerge: 10 | - patches/common-settings.yaml 11 | - patches/external-access.yaml 12 | 13 | -------------------------------------------------------------------------------- /gce-reduced/patches/common-settings.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: common-settings 8 | namespace: opennms 9 | data: 10 | DOMAIN: gce.agalue.net 11 | TIMEZONE: America/New_York 12 | OPENNMS_INSTANCE_ID: K8S 13 | CASSANDRA_CLUSTER_NAME: OpenNMS 14 | CASSANDRA_DC: Main 15 | CASSANDRA_REPLICATION_FACTOR: "1" 16 | ELASTIC_INDEX_STRATEGY_FLOWS: daily 17 | ELASTIC_REPLICATION_FACTOR: "0" 18 | ELASTIC_NUM_SHARDS: "1" 19 | KAFKA_NUM_PARTITIONS: "1" 20 | KAFKA_REPLICATION_FACTOR: "1" 21 | MINION_LOCATION: Kubernetes 22 | -------------------------------------------------------------------------------- /gce-reduced/patches/external-access.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: networking.k8s.io/v1 5 | kind: Ingress 6 | metadata: 7 | name: onms-ingress 8 | namespace: opennms 9 | spec: 10 | tls: 11 | - secretName: opennms-ingress-cert 12 | hosts: 13 | - onms.gce.agalue.net 14 | - grafana.gce.agalue.net 15 | - kafka-manager.gce.agalue.net 16 | - tracing.gce.agalue.net 17 | rules: 18 | - host: onms.gce.agalue.net 19 | http: 20 | paths: 21 | - path: / 22 | pathType: Prefix 23 | backend: 24 | service: 25 | name: opennms-core 26 | port: 27 | number: 8980 28 | - host: grafana.gce.agalue.net 29 | http: 30 | paths: 31 | - path: / 32 | pathType: Prefix 33 | backend: 34 | service: 35 | name: grafana 36 | port: 37 | number: 3000 38 | - host: kafka-manager.gce.agalue.net 39 | http: 40 | paths: 41 | - path: / 42 | pathType: Prefix 43 | backend: 44 | service: 45 | name: kafka-manager 46 | port: 47 | number: 9000 48 | - host: tracing.gce.agalue.net 49 | http: 50 | paths: 51 | - path: / 52 | pathType: Prefix 53 | backend: 54 | service: 55 | name: onms-tracing-query 56 | port: 57 | number: 16686 58 | 59 | --- 60 | apiVersion: networking.k8s.io/v1 61 | kind: Ingress 62 | metadata: 63 | name: grpc-ingress 64 | namespace: opennms 65 | spec: 66 | tls: 67 | - secretName: grpc-ingress-cert 68 | hosts: 69 | - grpc.gce.agalue.net 70 | rules: 71 | - host: grpc.gce.agalue.net 72 | http: 73 | paths: 74 | - path: / 75 | pathType: Prefix 76 | backend: 77 | service: 78 | name: grpc-server 79 | port: 80 | number: 8990 81 | -------------------------------------------------------------------------------- /gce/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | bases: 5 | - ../manifests 6 | 7 | patchesStrategicMerge: 8 | - patches/common-settings.yaml 9 | - patches/external-access.yaml 10 | -------------------------------------------------------------------------------- /gce/patches/common-settings.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: common-settings 8 | namespace: opennms 9 | data: 10 | DOMAIN: gce.agalue.net 11 | TIMEZONE: America/New_York 12 | OPENNMS_INSTANCE_ID: K8S 13 | CASSANDRA_CLUSTER_NAME: OpenNMS 14 | CASSANDRA_DC: Main 15 | CASSANDRA_REPLICATION_FACTOR: "2" 16 | ELASTIC_INDEX_STRATEGY_FLOWS: daily 17 | ELASTIC_REPLICATION_FACTOR: "2" 18 | ELASTIC_NUM_SHARDS: "6" 19 | KAFKA_NUM_PARTITIONS: "6" 20 | KAFKA_REPLICATION_FACTOR: "2" 21 | MINION_LOCATION: Kubernetes 22 | -------------------------------------------------------------------------------- /gce/patches/external-access.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: networking.k8s.io/v1 5 | kind: Ingress 6 | metadata: 7 | name: onms-ingress 8 | namespace: opennms 9 | spec: 10 | tls: 11 | - secretName: opennms-ingress-cert 12 | hosts: 13 | - onms.gce.agalue.net 14 | - onmsui.gce.agalue.net 15 | - cassandra-reaper.gce.agalue.net 16 | - grafana.gce.agalue.net 17 | - hasura.gce.agalue.net 18 | - kafka-manager.gce.agalue.net 19 | - kibana.gce.agalue.net 20 | - elastichq.gce.agalue.net 21 | - tracing.gce.agalue.net 22 | - flink.gce.agalue.net 23 | rules: 24 | - host: onms.gce.agalue.net 25 | http: 26 | paths: 27 | - path: / 28 | pathType: Prefix 29 | backend: 30 | service: 31 | name: opennms-core 32 | port: 33 | number: 8980 34 | - host: onmsui.gce.agalue.net 35 | http: 36 | paths: 37 | - path: / 38 | pathType: Prefix 39 | backend: 40 | service: 41 | name: opennms-ui 42 | port: 43 | number: 8980 44 | - path: /opennms/nrt 45 | pathType: Prefix 46 | backend: 47 | service: 48 | name: opennms-core 49 | port: 50 | number: 8980 51 | - host: cassandra-reaper.gce.agalue.net 52 | http: 53 | paths: 54 | - path: / 55 | pathType: Prefix 56 | backend: 57 | service: 58 | name: cassandra-reaper 59 | port: 60 | number: 8080 61 | - host: grafana.gce.agalue.net 62 | http: 63 | paths: 64 | - path: / 65 | pathType: Prefix 66 | backend: 67 | service: 68 | name: grafana 69 | port: 70 | number: 3000 71 | - host: hasura.gce.agalue.net 72 | http: 73 | paths: 74 | - path: / 75 | pathType: Prefix 76 | backend: 77 | service: 78 | name: hasura 79 | port: 80 | number: 8080 81 | - host: kafka-manager.gce.agalue.net 82 | http: 83 | paths: 84 | - path: / 85 | pathType: Prefix 86 | backend: 87 | service: 88 | name: kafka-manager 89 | port: 90 | number: 9000 91 | - host: kibana.gce.agalue.net 92 | http: 93 | paths: 94 | - path: / 95 | pathType: Prefix 96 | backend: 97 | service: 98 | name: kibana 99 | port: 100 | number: 5601 101 | - host: elastichq.gce.agalue.net 102 | http: 103 | paths: 104 | - path: / 105 | pathType: Prefix 106 | backend: 107 | service: 108 | name: elastichq 109 | port: 110 | number: 5000 111 | - host: tracing.gce.agalue.net 112 | http: 113 | paths: 114 | - path: / 115 | pathType: Prefix 116 | backend: 117 | service: 118 | name: onms-tracing-query 119 | port: 120 | number: 16686 121 | - host: flink.gce.agalue.net 122 | http: 123 | paths: 124 | - path: / 125 | pathType: Prefix 126 | backend: 127 | service: 128 | name: flink-jobmanager 129 | port: 130 | number: 8081 131 | 132 | --- 133 | apiVersion: networking.k8s.io/v1 134 | kind: Ingress 135 | metadata: 136 | name: grpc-ingress 137 | namespace: opennms 138 | spec: 139 | tls: 140 | - secretName: grpc-ingress-cert 141 | hosts: 142 | - grpc.gce.agalue.net 143 | rules: 144 | - host: grpc.gce.agalue.net 145 | http: 146 | paths: 147 | - path: / 148 | pathType: Prefix 149 | backend: 150 | service: 151 | name: grpc-server 152 | port: 153 | number: 8990 154 | -------------------------------------------------------------------------------- /manifests/cassandra.reaper.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # This is an optional component 4 | # 5 | # http://cassandra-reaper.io/docs/configuration/docker_vars/ 6 | # Main UI: https://cassandra-reaper.$YOUR_DOMAIN/webui/ 7 | 8 | --- 9 | apiVersion: v1 10 | kind: Service 11 | metadata: 12 | name: cassandra-reaper 13 | namespace: opennms 14 | labels: 15 | app: cassandra-reaper 16 | spec: 17 | clusterIP: None 18 | ports: 19 | - port: 8080 20 | name: http 21 | - port: 8081 22 | name: http-admin 23 | selector: 24 | app: cassandra-reaper 25 | 26 | --- 27 | apiVersion: apps/v1 28 | kind: Deployment 29 | metadata: 30 | name: cassandra-reaper 31 | namespace: opennms 32 | labels: 33 | app: cassandra-reaper 34 | spec: 35 | replicas: 1 36 | selector: 37 | matchLabels: 38 | app: cassandra-reaper 39 | template: 40 | metadata: 41 | labels: 42 | app: cassandra-reaper 43 | spec: 44 | initContainers: 45 | - name: dependencies 46 | image: waisbrot/wait 47 | imagePullPolicy: IfNotPresent 48 | env: 49 | - name: TARGETS 50 | value: cassandra.opennms.svc.cluster.local:9042 51 | - name: TIMEOUT 52 | value: '900' 53 | - name: init-database 54 | image: cassandra:4.0 55 | imagePullPolicy: IfNotPresent 56 | command: [ sh, -c, 'cqlsh -e "$CQL" $CASSANDRA_HOST' ] 57 | env: 58 | - name: CASSANDRA_HOST 59 | value: cassandra.opennms.svc.cluster.local 60 | - name: CASSANDRA_DC 61 | valueFrom: 62 | configMapKeyRef: 63 | key: CASSANDRA_DC 64 | name: common-settings 65 | - name: REP_FACTOR 66 | valueFrom: 67 | configMapKeyRef: 68 | key: CASSANDRA_REPLICATION_FACTOR 69 | name: common-settings 70 | - name: CQL 71 | value: "CREATE KEYSPACE IF NOT EXISTS reaper_db WITH replication = {'class' : 'NetworkTopologyStrategy', '$(CASSANDRA_DC)' : $(REP_FACTOR) };" 72 | containers: 73 | - name: cassandra-reaper 74 | image: thelastpickle/cassandra-reaper:2.3.1 75 | imagePullPolicy: IfNotPresent 76 | env: 77 | - name: TZ 78 | valueFrom: 79 | configMapKeyRef: 80 | key: TIMEZONE 81 | name: common-settings 82 | - name: REAPER_STORAGE_TYPE 83 | value: cassandra 84 | - name: REAPER_CASS_CONTACT_POINTS 85 | value: "[ cassandra.opennms.svc.cluster.local ]" 86 | - name: REAPER_CASS_CLUSTER_NAME 87 | valueFrom: 88 | configMapKeyRef: 89 | key: CASSANDRA_CLUSTER_NAME 90 | name: common-settings 91 | - name: REAPER_JMX_AUTH_USERNAME # See ConfigMap/cassandra-config from cassandra.yaml 92 | value: cassandra 93 | - name: REAPER_JMX_AUTH_PASSWORD # See ConfigMap/cassandra-config from cassandra.yaml 94 | value: cassandra 95 | - name: REAPER_CASS_KEYSPACE # See initContainers:init-database 96 | value: reaper_db 97 | ports: 98 | - containerPort: 8080 99 | name: http 100 | - containerPort: 8081 101 | name: http-admin 102 | resources: 103 | limits: 104 | cpu: 200m 105 | memory: 1Gi 106 | requests: 107 | cpu: 50m 108 | memory: 512Mi 109 | readinessProbe: 110 | tcpSocket: 111 | port: http 112 | initialDelaySeconds: 30 113 | periodSeconds: 15 114 | livenessProbe: 115 | tcpSocket: 116 | port: http 117 | initialDelaySeconds: 60 118 | periodSeconds: 60 119 | -------------------------------------------------------------------------------- /manifests/cert-manager.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | # For cloud providers / public access 4 | --- 5 | apiVersion: cert-manager.io/v1 6 | kind: Issuer 7 | metadata: 8 | name: letsencrypt-prod 9 | namespace: opennms 10 | spec: 11 | acme: 12 | server: https://acme-v02.api.letsencrypt.org/directory 13 | email: agalue@opennms.org # Warning: use your own email 14 | privateKeySecretRef: 15 | name: letsencrypt-prod 16 | solvers: 17 | - http01: 18 | ingress: 19 | class: nginx 20 | 21 | # For private CA 22 | --- 23 | apiVersion: cert-manager.io/v1 24 | kind: Issuer 25 | metadata: 26 | name: selfsigned-issuer 27 | namespace: opennms 28 | spec: 29 | selfSigned: {} 30 | 31 | # Root private CA 32 | --- 33 | apiVersion: cert-manager.io/v1 34 | kind: Certificate 35 | metadata: 36 | name: onms-ca 37 | namespace: opennms 38 | spec: 39 | isCA: true 40 | commonName: onms-system 41 | secretName: onms-ca 42 | subject: 43 | organizations: 44 | - OpenNMS 45 | issuerRef: 46 | name: selfsigned-issuer 47 | kind: Issuer 48 | group: cert-manager.io 49 | 50 | # For Local Deployments and Minion Authentication 51 | --- 52 | apiVersion: cert-manager.io/v1 53 | kind: Issuer 54 | metadata: 55 | name: onms-ca-issuer 56 | namespace: opennms 57 | spec: 58 | ca: 59 | secretName: onms-ca 60 | 61 | # Minion Client Certificate 62 | --- 63 | apiVersion: cert-manager.io/v1 64 | kind: Certificate 65 | metadata: 66 | name: minion-cert 67 | namespace: opennms 68 | spec: 69 | commonName: minion 70 | secretName: minion-cert 71 | privateKey: 72 | algorithm: RSA 73 | size: 2048 74 | subject: 75 | organizations: 76 | - OpenNMS 77 | issuerRef: 78 | name: onms-ca-issuer 79 | kind: Issuer 80 | group: cert-manager.io 81 | -------------------------------------------------------------------------------- /manifests/config/create-topics.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @author Alejandro Galue 3 | 4 | CFG="/tmp/client.properties" 5 | cat < $CFG 6 | security.protocol=SASL_PLAINTEXT 7 | sasl.mechanism=PLAIN 8 | sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="$KAFKA_CLIENT_USER" password="$KAFKA_CLIENT_PASSWORD"; 9 | EOF 10 | 11 | for TOPIC in $CREATE_TOPICS; do 12 | echo "Creating topic $TOPIC ..." 13 | JMX_PORT='' kafka-topics.sh --bootstrap-server $KAFKA_SERVER:9092 \ 14 | --command-config=$CFG \ 15 | --create --if-not-exists --topic $TOPIC \ 16 | --partitions $KAFKA_CFG_NUM_PARTITIONS \ 17 | --replication-factor $KAFKA_CFG_DEFAULT_REPLICATION_FACTOR 18 | done -------------------------------------------------------------------------------- /manifests/config/grafana-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # @author Alejandro Galue 3 | # 4 | # Purpose: 5 | # - Create a PostgreSQL database and user for grafana, if it doesn't exist. 6 | # 7 | # Mandatory Environment variables: 8 | # - PGHOST 9 | # - PGPORT 10 | # - PGUSER 11 | # - PGPASSWORD 12 | # - GF_DATABASE_NAME 13 | # - GF_DATABASE_USER 14 | # - GF_DATABASE_PASSWORD 15 | 16 | until pg_isready; do 17 | echo "$(date) Waiting for postgresql host ${PGHOST}..." 18 | sleep 2 19 | done 20 | 21 | if psql -lqt | cut -d \| -f 1 | grep -qw "${GF_DATABASE_NAME}"; then 22 | TABLES=$(psql -qtAX -d "${GF_DATABASE_NAME}" -c "select count(*) from pg_stat_user_tables;") 23 | echo "Grafana database already exists on ${PGHOST}." 24 | echo "There are ${TABLES} tables on it, skipping..." 25 | else 26 | echo "Creating grafana user and database on ${PGHOST}..." 27 | createdb -E UTF-8 "${GF_DATABASE_NAME}" 28 | createuser "${GF_DATABASE_USER}" 29 | psql -c "alter role ${GF_DATABASE_USER} with password '${GF_DATABASE_PASSWORD}';" 30 | psql -c "alter user ${GF_DATABASE_USER} set search_path to ${GF_DATABASE_NAME},public;" 31 | psql -c "grant all on database ${GF_DATABASE_NAME} to ${GF_DATABASE_USER};" 32 | fi 33 | -------------------------------------------------------------------------------- /manifests/config/onms-alec-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @author Alejandro Galue 3 | # 4 | # ALEC is required only for advanced alarms correlation. 5 | # 6 | # Requirements: 7 | # - Horizon 27 or newer is required. 8 | # - ALEC 1.1.0 or newer is required. 9 | # - Overlay volume mounted at /etc-overlay 10 | # 11 | # Environment variables: 12 | # - INSTANCE_ID 13 | # - ZOOKEEPER_SERVER 14 | # - KAFKA_SERVER 15 | # - KAFKA_SASL_USERNAME 16 | # - KAFKA_SASL_PASSWORD 17 | 18 | # To avoid issues with OpenShift 19 | umask 002 20 | 21 | OVERLAY=/etc-overlay 22 | SENTINEL_HOME=/opt/sentinel 23 | 24 | # Configure the instance ID 25 | # Required when having multiple OpenNMS backends sharing the same Kafka cluster. 26 | CUSTOM_PROPERTIES=${OVERLAY}/custom.system.properties 27 | if [[ ${INSTANCE_ID} ]]; then 28 | echo "Configuring Instance ID..." 29 | cat <> ${CUSTOM_PROPERTIES} 30 | # Used for Kafka Topics 31 | org.opennms.instance.id=${INSTANCE_ID} 32 | EOF 33 | else 34 | INSTANCE_ID="OpenNMS" 35 | fi 36 | 37 | FEATURES_DIR=${OVERLAY}/featuresBoot.d 38 | mkdir -p ${FEATURES_DIR} 39 | echo "Configuring Features..." 40 | 41 | cat < ${FEATURES_DIR}/alec.boot 42 | sentinel-core 43 | sentinel-coordination-zookeeper 44 | alec-sentinel-distributed wait-for-kar=opennms-alec-plugin 45 | EOF 46 | 47 | if [[ ${ZOOKEEPER_SERVER} ]]; then 48 | echo "Configure ZooKeeper for distributed coordination..." 49 | 50 | cat < ${OVERLAY}/org.opennms.features.distributed.coordination.zookeeper.cfg 51 | connectString=${ZOOKEEPER_SERVER}:2181 52 | EOF 53 | fi 54 | 55 | if [[ ${KAFKA_SERVER} ]]; then 56 | echo "Configuring Kafka..." 57 | 58 | if [[ ${KAFKA_SASL_USERNAME} && ${KAFKA_SASL_PASSWORD} ]]; then 59 | read -r -d '' KAFKA_SASL < ${OVERLAY}/org.opennms.core.ipc.sink.kafka.consumer.cfg 67 | bootstrap.servers=${KAFKA_SERVER}:9092 68 | ${KAFKA_SASL} 69 | EOF 70 | 71 | cat < ${OVERLAY}/org.opennms.alec.datasource.opennms.kafka.producer.cfg 72 | bootstrap.servers=${KAFKA_SERVER}:9092 73 | ${KAFKA_SASL} 74 | EOF 75 | 76 | cat < ${OVERLAY}/org.opennms.alec.datasource.opennms.kafka.streams.cfg 77 | bootstrap.servers=${KAFKA_SERVER}:9092 78 | application.id=${INSTANCE_ID}_alec_datasource 79 | commit.interval.ms=5000 80 | ${KAFKA_SASL} 81 | EOF 82 | 83 | cat < ${OVERLAY}/org.opennms.alec.datasource.opennms.kafka.cfg 84 | # Make sure to configure the topics on OpenNMS the same way 85 | eventSinkTopic=${INSTANCE_ID}.Sink.Events 86 | inventoryTopic=${INSTANCE_ID}_alec_inventory 87 | nodeTopic=${INSTANCE_ID}_nodes 88 | alarmTopic=${INSTANCE_ID}_alarms 89 | alarmFeedbackTopic=${INSTANCE_ID}_alarms_feedback 90 | edgesTopic=${INSTANCE_ID}_edges 91 | EOF 92 | fi 93 | -------------------------------------------------------------------------------- /manifests/config/onms-helm-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # @author Alejandro Galue 3 | # 4 | # Purpose: 5 | # - Enable the Helm plugin and initialize the data sources, if Helm is disable. 6 | # 7 | # Mandatory Environment variables: 8 | # - GF_SECURITY_ADMIN_PASSWORD 9 | # - GRAFANA_URL 10 | 11 | GRAFANA_AUTH="admin:${GF_SECURITY_ADMIN_PASSWORD}" 12 | HELM_URL="${GRAFANA_URL}/api/plugins/opennms-helm-app/settings" 13 | DS_URL="${GRAFANA_URL}/api/datasources" 14 | 15 | JSON_FILE=/tmp/data.json 16 | cat < ${JSON_FILE} 17 | { 18 | "name": "opennms-performance", 19 | "type": "opennms-helm-performance-datasource", 20 | "access": "proxy", 21 | "url": "${ONMS_URL}", 22 | "basicAuth": true, 23 | "basicAuthUser": "${ONMS_USER}", 24 | "basicAuthPassword": "${ONMS_PASSWD}" 25 | } 26 | EOF 27 | 28 | until curl --output /dev/null --silent --head --fail "${GRAFANA_URL}"; do 29 | echo "$(date) Waiting for grafana to be ready on ${GRAFANA_URL} ..." 30 | sleep 5 31 | done 32 | 33 | echo "$(date) Checking if OpenNMS Helm is enabled..." 34 | if curl -u "${GRAFANA_AUTH}" "${HELM_URL}" 2>/dev/null | grep -q '"enabled":false'; then 35 | echo 36 | echo "$(date) Enabling OpenNMS Helm..." 37 | curl -u "${GRAFANA_AUTH}" -XPOST "${HELM_URL}" -d "id=opennms-helm-app&enabled=true" 2>/dev/null 38 | echo 39 | echo "$(date) Adding data source for performance metrics..." 40 | curl -u "${GRAFANA_AUTH}" -H 'Content-Type: application/json' -XPOST -d @${JSON_FILE} "${DS_URL}" 2>/dev/null 41 | echo 42 | echo "$(date) Adding data source for entities..." 43 | sed -i -r 's/-performance/-entity/g' ${JSON_FILE} 44 | curl -u "${GRAFANA_AUTH}" -H 'Content-Type: application/json' -XPOST -d @${JSON_FILE} "${DS_URL}" 2>/dev/null 45 | echo 46 | echo "$(date) Adding data source for flows..." 47 | sed -i -r 's/-entity/-flow/g' ${JSON_FILE} 48 | curl -u "${GRAFANA_AUTH}" -H 'Content-Type: application/json' -XPOST -d @${JSON_FILE} "${DS_URL}" 2>/dev/null 49 | echo 50 | else 51 | echo "$(date) OpenNMS Helm was already enabled and configured." 52 | fi 53 | 54 | rm -f ${JSON_FILE} 55 | -------------------------------------------------------------------------------- /manifests/config/onms-nephron-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # @author Alejandro Galue 3 | # 4 | # Purpose: 5 | # - Generate the client settings for Kafka 6 | # 7 | # Environment variables: 8 | # - KAFKA_SASL_USERNAME 9 | # - KAFKA_SASL_PASSWORD 10 | 11 | cat < /data/client.properties 12 | security.protocol=SASL_PLAINTEXT 13 | sasl.mechanism=PLAIN 14 | sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="${KAFKA_SASL_USERNAME}" password="${KAFKA_SASL_PASSWORD}"; 15 | EOF 16 | -------------------------------------------------------------------------------- /manifests/elastic-hq.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # This is an optional component 4 | 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | name: elastichq 10 | namespace: opennms 11 | labels: 12 | app: elastichq 13 | spec: 14 | clusterIP: None 15 | ports: 16 | - port: 5000 17 | name: http 18 | selector: 19 | app: elastichq 20 | 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | name: elastichq 26 | namespace: opennms 27 | labels: 28 | app: elastichq 29 | spec: 30 | replicas: 1 31 | selector: 32 | matchLabels: 33 | app: elastichq 34 | template: 35 | metadata: 36 | labels: 37 | app: elastichq 38 | spec: 39 | initContainers: 40 | - name: dependencies 41 | image: waisbrot/wait 42 | imagePullPolicy: IfNotPresent 43 | env: 44 | - name: TARGETS 45 | value: esdata.opennms.svc.cluster.local:9200 46 | - name: TIMEOUT 47 | value: '900' 48 | containers: 49 | - name: elasticsearch-hq 50 | image: elastichq/elasticsearch-hq:latest 51 | imagePullPolicy: IfNotPresent 52 | ports: 53 | - containerPort: 5000 54 | name: http 55 | env: 56 | - name: HQ_DEFAULT_URL 57 | value: http://esdata.opennms.svc.cluster.local:9200 58 | resources: 59 | limits: 60 | cpu: 200m 61 | memory: 256Mi 62 | requests: 63 | cpu: 100m 64 | memory: 128Mi 65 | readinessProbe: 66 | tcpSocket: 67 | port: http 68 | initialDelaySeconds: 30 69 | periodSeconds: 15 70 | livenessProbe: 71 | tcpSocket: 72 | port: http 73 | initialDelaySeconds: 60 74 | periodSeconds: 60 75 | -------------------------------------------------------------------------------- /manifests/elasticsearch.curator.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # This is an optional component 4 | 5 | --- 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: curator-config 10 | namespace: opennms 11 | labels: 12 | app: curator 13 | data: 14 | config.yaml: |+ 15 | client: 16 | hosts: 17 | - esdata.opennms.svc.cluster.local 18 | port: 9200 19 | url_prefix: 20 | use_ssl: False 21 | certificate: 22 | client_cert: 23 | client_key: 24 | ssl_no_validate: False 25 | http_auth: 26 | timeout: 30 27 | master_only: True 28 | logging: 29 | loglevel: INFO 30 | logfile: 31 | logformat: default 32 | blacklist: ['elasticsearch', 'urllib3'] 33 | actions.yaml: |+ 34 | actions: 35 | 1: 36 | action: forcemerge 37 | description: Force merge Netflow indices 38 | options: 39 | max_num_segments: 1 40 | delay: 120 41 | timneout_override: 42 | continue_if_exception: False 43 | disable_action: False 44 | filters: 45 | - filtertype: pattern 46 | kind: prefix 47 | value: netflow- 48 | exclude: 49 | - filtertype: age 50 | source: name 51 | direction: older 52 | timestring: '%Y-%m-%d-%H' 53 | unit: hours 54 | unit_count: 12 55 | exclude: 56 | - filtertype: forcemerged 57 | max_num_segments: 1 58 | exclude: 59 | 2: 60 | action: delete_indices 61 | description: Delete indices older than 30 days. 62 | options: 63 | ignore_empty_list: True 64 | disable_action: False 65 | filters: 66 | - filtertype: pattern 67 | kind: prefix 68 | value: netflow- 69 | - filtertype: age 70 | source: name 71 | direction: older 72 | timestring: '%Y-%m-%d-%H' 73 | unit: hours 74 | unit_count: 720 75 | 76 | --- 77 | apiVersion: batch/v1 78 | kind: CronJob 79 | metadata: 80 | name: curator-cron 81 | namespace: opennms 82 | labels: 83 | app: curator 84 | spec: 85 | schedule: '0 3 * * *' 86 | jobTemplate: 87 | metadata: 88 | name: curator-job 89 | labels: 90 | app: curator 91 | spec: 92 | template: 93 | metadata: 94 | labels: 95 | app: curator 96 | spec: 97 | restartPolicy: Never 98 | containers: 99 | - image: bobrik/curator:5.8.1 100 | name: delete-indices 101 | args: 102 | - --config 103 | - /config/config.yaml 104 | - /config/actions.yaml 105 | env: 106 | - name: TZ 107 | value: America/New_York 108 | volumeMounts: 109 | - name: curator-config 110 | mountPath: /config 111 | volumes: 112 | - name: curator-config 113 | configMap: 114 | name: curator-config 115 | -------------------------------------------------------------------------------- /manifests/elasticsearch.data.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # WARNING: 4 | # - The elasticsearch user (uid: 1000, gid: 1000) cannot be used in OpenShift by default. 5 | # - Elasticsearch expects to have very fast disks for the PVC to avoid performance issues. 6 | # - Internet access from the container is required to retrieve the Drift Plugin binaries from GitHub. 7 | 8 | --- 9 | apiVersion: v1 10 | kind: Service 11 | metadata: 12 | name: esdata 13 | namespace: opennms 14 | labels: 15 | app: elasticsearch 16 | role: esdata 17 | spec: 18 | clusterIP: None 19 | ports: 20 | - port: 9200 21 | name: http 22 | - port: 9300 23 | name: transport 24 | selector: 25 | role: esdata 26 | 27 | --- 28 | apiVersion: apps/v1 29 | kind: StatefulSet 30 | metadata: 31 | name: esdata 32 | namespace: opennms 33 | labels: 34 | app: elasticsearch 35 | role: esdata 36 | spec: 37 | serviceName: esdata 38 | replicas: 3 39 | selector: 40 | matchLabels: 41 | role: esdata 42 | template: 43 | metadata: 44 | labels: 45 | app: elasticsearch 46 | role: esdata 47 | spec: 48 | terminationGracePeriodSeconds: 300 49 | securityContext: # In order to be able to write data as non-root on the volumes 50 | fsGroup: 1000 # Default elasticsearch user 51 | initContainers: 52 | - name: init-sysctl 53 | image: busybox 54 | command: 55 | - sysctl 56 | - -w 57 | - vm.max_map_count=262144 58 | securityContext: 59 | privileged: true 60 | - name: onms-plugin 61 | image: busybox 62 | command: [ sh, -c ] 63 | args: [ "wget $(PLUGIN_URL) && unzip elasticsearch-drift-plugin-$(PLUGIN_VERSION).zip -d /plugin/" ] 64 | env: 65 | - name: PLUGIN_VERSION # Must match the chosen Elasticsearch version 66 | value: '7.6.2' 67 | - name: PLUGIN_URL 68 | value: https://github.com/OpenNMS/elasticsearch-drift-plugin/releases/download/v$(PLUGIN_VERSION)/elasticsearch-drift-plugin-$(PLUGIN_VERSION).zip 69 | volumeMounts: 70 | - name: onms-plugin-dir 71 | mountPath: /plugin 72 | - name: dependencies 73 | image: waisbrot/wait 74 | imagePullPolicy: IfNotPresent 75 | env: 76 | - name: TARGETS 77 | value: esmaster.opennms.svc.cluster.local:9200 78 | - name: TIMEOUT 79 | value: '900' 80 | containers: 81 | - name: esdata 82 | image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2 83 | imagePullPolicy: IfNotPresent 84 | securityContext: 85 | runAsUser: 1000 86 | capabilities: 87 | add: 88 | - IPC_LOCK 89 | - SYS_RESOURCE 90 | ports: 91 | - containerPort: 9200 92 | name: http 93 | - containerPort: 9300 94 | name: transport 95 | env: 96 | - name: TZ 97 | valueFrom: 98 | configMapKeyRef: 99 | key: TIMEZONE 100 | name: common-settings 101 | - name: cluster.name 102 | value: OpenNMS 103 | - name: node.name 104 | valueFrom: 105 | fieldRef: 106 | fieldPath: metadata.name 107 | - name: xpack.security.enabled 108 | value: 'false' # Enabling security requires a license 109 | - name: discovery.seed_hosts 110 | value: esmaster.opennms.svc.cluster.local 111 | - name: node.master 112 | value: 'false' 113 | - name: node.data 114 | value: 'true' 115 | - name: node.ingest # Required, even if data won't be transformed before index it (to avoid having dedicated ingest nodes) 116 | value: 'true' 117 | - name: http.cors.enabled 118 | value: 'true' 119 | - name: http.cors.allow-origin 120 | value: '*' 121 | - name: xpack.monitoring.collection.enabled 122 | value: 'true' 123 | - name: search.max_buckets # To avoid "Too many buckets exception" in Grafana 124 | value: '50000' 125 | - name: MEM_TOTAL_MB 126 | valueFrom: 127 | resourceFieldRef: 128 | resource: requests.memory 129 | divisor: 1Mi 130 | - name: ES_JAVA_OPTS # TODO jvm.options default will be used if Xms/Xmx are not passed 131 | value: -Xms$(MEM_TOTAL_MB)m -Xmx$(MEM_TOTAL_MB)m 132 | - name: ELASTIC_PASSWORD 133 | valueFrom: 134 | secretKeyRef: 135 | key: ELASTICSEARCH_PASSWORD 136 | name: onms-passwords 137 | volumeMounts: 138 | - name: data 139 | mountPath: /usr/share/elasticsearch/data 140 | - name: onms-plugin-dir 141 | mountPath: /usr/share/elasticsearch/plugins/drift 142 | resources: 143 | limits: 144 | cpu: '4' 145 | memory: 4Gi 146 | requests: 147 | cpu: '2' 148 | memory: 2Gi 149 | readinessProbe: 150 | tcpSocket: 151 | port: http 152 | initialDelaySeconds: 30 153 | periodSeconds: 15 154 | livenessProbe: 155 | tcpSocket: 156 | port: http 157 | initialDelaySeconds: 60 158 | periodSeconds: 60 159 | volumes: 160 | - name: onms-plugin-dir 161 | emptyDir: {} 162 | volumeClaimTemplates: 163 | - metadata: 164 | name: data 165 | spec: 166 | accessModes: 167 | - ReadWriteOnce 168 | resources: 169 | requests: 170 | storage: 20Gi 171 | -------------------------------------------------------------------------------- /manifests/elasticsearch.master.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # WARNING: 4 | # - The elasticsearch user (uid: 1000, gid: 1000) cannot be used in OpenShift by default. 5 | 6 | --- 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: esmaster 11 | namespace: opennms 12 | labels: 13 | app: elasticsearch 14 | role: esmaster 15 | spec: 16 | clusterIP: None 17 | ports: 18 | - port: 9200 19 | name: http 20 | - port: 9300 21 | name: transport 22 | selector: 23 | role: esmaster 24 | 25 | --- 26 | apiVersion: apps/v1 27 | kind: StatefulSet 28 | metadata: 29 | name: esmaster 30 | namespace: opennms 31 | labels: 32 | app: elasticsearch 33 | role: esmaster 34 | spec: 35 | serviceName: esmaster 36 | replicas: 3 # The solution is designed for 3 masters only 37 | podManagementPolicy: Parallel 38 | selector: 39 | matchLabels: 40 | role: esmaster 41 | template: 42 | metadata: 43 | labels: 44 | app: elasticsearch 45 | role: esmaster 46 | spec: 47 | terminationGracePeriodSeconds: 300 48 | securityContext: # In order to be able to write data as non-root on the volumes 49 | fsGroup: 1000 # Default elasticsearch user 50 | initContainers: 51 | - name: init-sysctl 52 | image: busybox 53 | command: 54 | - sysctl 55 | - -w 56 | - vm.max_map_count=262144 57 | securityContext: 58 | privileged: true 59 | containers: 60 | - name: esmaster 61 | image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2 62 | imagePullPolicy: IfNotPresent 63 | securityContext: 64 | runAsUser: 1000 65 | capabilities: 66 | add: 67 | - IPC_LOCK 68 | - SYS_RESOURCE 69 | ports: 70 | - containerPort: 9200 71 | name: http 72 | - containerPort: 9300 73 | name: transport 74 | env: 75 | - name: TZ 76 | valueFrom: 77 | configMapKeyRef: 78 | key: TIMEZONE 79 | name: common-settings 80 | - name: cluster.name 81 | value: OpenNMS 82 | - name: node.name 83 | valueFrom: 84 | fieldRef: 85 | fieldPath: metadata.name 86 | - name: xpack.security.enabled 87 | value: 'false' # Enabling security requires a license 88 | - name: discovery.seed_hosts 89 | value: esmaster.opennms.svc.cluster.local 90 | - name: cluster.initial_master_nodes 91 | value: esmaster-0,esmaster-1,esmaster-2 92 | - name: node.master 93 | value: 'true' 94 | - name: node.data 95 | value: 'false' 96 | - name: node.ingest 97 | value: 'false' 98 | - name: xpack.monitoring.collection.enabled 99 | value: 'true' 100 | - name: MEM_TOTAL_MB 101 | valueFrom: 102 | resourceFieldRef: 103 | resource: requests.memory 104 | divisor: 1Mi 105 | - name: ES_JAVA_OPTS # TODO jvm.options default will be used if Xms/Xmx are not passed 106 | value: -Xms$(MEM_TOTAL_MB)m -Xmx$(MEM_TOTAL_MB)m 107 | - name: ELASTIC_PASSWORD 108 | valueFrom: 109 | secretKeyRef: 110 | key: ELASTICSEARCH_PASSWORD 111 | name: onms-passwords 112 | volumeMounts: 113 | - name: data 114 | mountPath: /usr/share/elasticsearch/data 115 | resources: 116 | limits: 117 | cpu: '1' 118 | memory: 2Gi 119 | requests: 120 | cpu: 500m 121 | memory: 1Gi 122 | readinessProbe: 123 | tcpSocket: 124 | port: transport 125 | initialDelaySeconds: 30 126 | periodSeconds: 15 127 | livenessProbe: 128 | tcpSocket: 129 | port: http 130 | initialDelaySeconds: 60 131 | periodSeconds: 60 132 | volumeClaimTemplates: 133 | - metadata: 134 | name: data 135 | spec: 136 | accessModes: 137 | - ReadWriteOnce 138 | resources: 139 | requests: 140 | storage: 5Gi 141 | 142 | -------------------------------------------------------------------------------- /manifests/event-watcher.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # Source: https://github.com/OpenNMS/opennms-drift-kubernetes/tree/master/tools/event-watcher 3 | # 4 | # A simple K8s Controller that generate OpenNMS events upon reception of certain K8s events. 5 | # The event watcher requires a service account with cluster-level access to get, list, and watch pods, services, and events. 6 | 7 | --- 8 | apiVersion: v1 9 | kind: ServiceAccount 10 | metadata: 11 | name: event-watcher-user 12 | namespace: opennms 13 | 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: ClusterRole 17 | metadata: 18 | name: event-watcher-role 19 | rules: 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - pods 24 | - services 25 | - events 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | 31 | --- 32 | apiVersion: rbac.authorization.k8s.io/v1 33 | kind: ClusterRoleBinding 34 | metadata: 35 | name: event-watcher-binding 36 | subjects: 37 | - kind: ServiceAccount 38 | name: event-watcher-user 39 | namespace: opennms 40 | roleRef: 41 | apiGroup: rbac.authorization.k8s.io 42 | kind: ClusterRole 43 | name: event-watcher-role 44 | 45 | --- 46 | apiVersion: apps/v1 47 | kind: Deployment 48 | metadata: 49 | name: event-watcher 50 | namespace: opennms 51 | spec: 52 | replicas: 1 # Do not scale 53 | selector: 54 | matchLabels: 55 | app: event-watcher 56 | template: 57 | metadata: 58 | labels: 59 | app: event-watcher 60 | spec: 61 | serviceAccountName: event-watcher-user 62 | initContainers: 63 | - name: dependencies 64 | image: waisbrot/wait 65 | imagePullPolicy: IfNotPresent 66 | env: 67 | - name: TARGETS 68 | value: opennms-core.opennms.svc.cluster.local:8980 69 | - name: TIMEOUT 70 | value: '900' 71 | containers: 72 | - name: event-watcher 73 | image: agalue/onms-k8s-watcher-go:latest 74 | imagePullPolicy: Always 75 | env: 76 | - name: TZ 77 | valueFrom: 78 | configMapKeyRef: 79 | key: TIMEZONE 80 | name: common-settings 81 | - name: ONMS_URL 82 | value: http://opennms-core.opennms.svc.cluster.local:8980/opennms 83 | - name: ONMS_USER 84 | value: admin 85 | - name: ONMS_PASSWD 86 | valueFrom: 87 | secretKeyRef: 88 | key: OPENNMS_UI_ADMIN_PASSWORD 89 | name: onms-passwords 90 | resources: 91 | limits: 92 | memory: 128Mi 93 | cpu: 100m 94 | -------------------------------------------------------------------------------- /manifests/external-access.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # With Kops, the dns-controller will automatically create entries on Route53 when --watch-ingress=true is applied. 4 | # With EKS, external-dns will create entries on Route53. 5 | # With GKE and AKS, wildcard entries on their respective DNS services are required. 6 | # 7 | # WARNING: 8 | # Please use your own domains. This is not intended to be used by any user. 9 | 10 | # Access to applications based on HTTP 11 | --- 12 | apiVersion: networking.k8s.io/v1 13 | kind: Ingress 14 | metadata: 15 | name: onms-ingress 16 | namespace: opennms 17 | annotations: 18 | ingress.kubernetes.io/affinity: cookie 19 | ingress.kubernetes.io/session-cookie-name: route 20 | ingress.kubernetes.io/session-cookie-hash: sha1 21 | nginx.ingress.kubernetes.io/ssl-redirect: 'true' 22 | cert-manager.io/issuer: letsencrypt-prod 23 | acme.cert-manager.io/http01-edit-in-place: 'true' 24 | spec: 25 | ingressClassName: nginx 26 | tls: 27 | - secretName: opennms-ingress-cert 28 | hosts: 29 | - onms.aws.agalue.net 30 | - onmsui.aws.agalue.net 31 | - cassandra-reaper.aws.agalue.net 32 | - grafana.aws.agalue.net 33 | - hasura.aws.agalue.net 34 | - kafka-manager.aws.agalue.net 35 | - kibana.aws.agalue.net 36 | - elastichq.aws.agalue.net 37 | - tracing.aws.agalue.net 38 | - flink.aws.agalue.net 39 | rules: 40 | - host: onms.aws.agalue.net 41 | http: 42 | paths: 43 | - path: / 44 | pathType: Prefix 45 | backend: 46 | service: 47 | name: opennms-core 48 | port: 49 | number: 8980 50 | - host: onmsui.aws.agalue.net 51 | http: 52 | paths: 53 | - path: / 54 | pathType: Prefix 55 | backend: 56 | service: 57 | name: opennms-ui 58 | port: 59 | number: 8980 60 | - path: /opennms/nrt 61 | pathType: Prefix 62 | backend: 63 | service: 64 | name: opennms-core 65 | port: 66 | number: 8980 67 | - host: cassandra-reaper.aws.agalue.net 68 | http: 69 | paths: 70 | - path: / 71 | pathType: Prefix 72 | backend: 73 | service: 74 | name: cassandra-reaper 75 | port: 76 | number: 8080 77 | - host: grafana.aws.agalue.net 78 | http: 79 | paths: 80 | - path: / 81 | pathType: Prefix 82 | backend: 83 | service: 84 | name: grafana 85 | port: 86 | number: 3000 87 | - host: hasura.aws.agalue.net 88 | http: 89 | paths: 90 | - path: / 91 | pathType: Prefix 92 | backend: 93 | service: 94 | name: hasura 95 | port: 96 | number: 8080 97 | - host: kafka-manager.aws.agalue.net 98 | http: 99 | paths: 100 | - path: / 101 | pathType: Prefix 102 | backend: 103 | service: 104 | name: kafka-manager 105 | port: 106 | number: 9000 107 | - host: kibana.aws.agalue.net 108 | http: 109 | paths: 110 | - path: / 111 | pathType: Prefix 112 | backend: 113 | service: 114 | name: kibana 115 | port: 116 | number: 5601 117 | - host: elastichq.aws.agalue.net 118 | http: 119 | paths: 120 | - path: / 121 | pathType: Prefix 122 | backend: 123 | service: 124 | name: elastichq 125 | port: 126 | number: 5000 127 | - host: tracing.aws.agalue.net 128 | http: 129 | paths: 130 | - path: / 131 | pathType: Prefix 132 | backend: 133 | service: 134 | name: onms-tracing-query 135 | port: 136 | number: 16686 137 | # Consider using a different ingress to enable authentication 138 | # https://kubernetes.github.io/ingress-nginx/examples/auth/basic/ 139 | - host: flink.aws.agalue.net 140 | http: 141 | paths: 142 | - path: / 143 | pathType: Prefix 144 | backend: 145 | service: 146 | name: flink-jobmanager 147 | port: 148 | number: 8081 149 | 150 | # Access to applications based on gRPC 151 | --- 152 | apiVersion: networking.k8s.io/v1 153 | kind: Ingress 154 | metadata: 155 | name: grpc-ingress 156 | namespace: opennms 157 | annotations: 158 | kubernetes.io/ingress.class: nginx 159 | cert-manager.io/issuer: letsencrypt-prod 160 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 161 | nginx.ingress.kubernetes.io/backend-protocol: GRPC 162 | nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" 163 | nginx.ingress.kubernetes.io/auth-tls-secret: "opennms/onms-ca" 164 | nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" 165 | nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "false" 166 | spec: 167 | tls: 168 | - secretName: grpc-ingress-cert 169 | hosts: 170 | - grpc.aws.agalue.net 171 | rules: 172 | - host: grpc.aws.agalue.net 173 | http: 174 | paths: 175 | - path: / 176 | pathType: Prefix 177 | backend: 178 | service: 179 | name: grpc-server 180 | port: 181 | number: 8990 182 | -------------------------------------------------------------------------------- /manifests/flink.job-manager.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: flink-jobmanager 8 | namespace: opennms 9 | spec: 10 | type: ClusterIP 11 | ports: 12 | - name: rpc 13 | port: 6123 14 | - name: blob 15 | port: 6124 16 | - name: query 17 | port: 6125 18 | - name: ui 19 | port: 8081 20 | selector: 21 | app: flink 22 | component: jobmanager 23 | 24 | --- 25 | apiVersion: apps/v1 26 | kind: StatefulSet 27 | metadata: 28 | name: flink-jobmanager 29 | namespace: opennms 30 | spec: 31 | replicas: 1 # Do not change it. This deployment is not designed to operate in HA mode 32 | serviceName: flink-jobmanager 33 | selector: 34 | matchLabels: 35 | app: flink 36 | component: jobmanager 37 | template: 38 | metadata: 39 | labels: 40 | app: flink 41 | component: jobmanager 42 | spec: 43 | containers: 44 | - name: jobmanager 45 | image: apache/flink:1.13-java11 46 | imagePullPolicy: IfNotPresent 47 | args: 48 | - jobmanager 49 | env: 50 | - name: TZ 51 | valueFrom: 52 | configMapKeyRef: 53 | key: TIMEZONE 54 | name: common-settings 55 | - name: FLINK_PROPERTIES 56 | value: | 57 | jobmanager.rpc.address: flink-jobmanager.opennms.svc.cluster.local 58 | taskmanager.data.port: 6121 59 | taskmanager.rpc.port: 6122 60 | jobmanager.rpc.port: 6123 61 | blob.server.port: 6124 62 | queryable-state.proxy.ports: 6125 63 | taskmanager.numberOfTaskSlots: 2 64 | parallelism.default: 2 65 | web.submit.enable: false 66 | web.cancel.enable: false 67 | ports: 68 | - containerPort: 6123 69 | name: rpc 70 | - containerPort: 6124 71 | name: blob 72 | - containerPort: 6125 73 | name: query 74 | - containerPort: 8081 75 | name: ui 76 | livenessProbe: 77 | tcpSocket: 78 | port: 6123 79 | initialDelaySeconds: 30 80 | periodSeconds: 60 81 | resources: 82 | limits: 83 | cpu: 200m 84 | memory: 2Gi 85 | -------------------------------------------------------------------------------- /manifests/flink.task-manager.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: flink-taskmanager 8 | namespace: opennms 9 | spec: 10 | clusterIP: None 11 | ports: 12 | - name: data 13 | port: 6121 14 | - name: rpc 15 | port: 6122 16 | - name: query 17 | port: 6125 18 | selector: 19 | app: flink 20 | component: taskmanager 21 | 22 | --- 23 | apiVersion: apps/v1 24 | kind: StatefulSet 25 | metadata: 26 | name: flink-tm 27 | namespace: opennms 28 | spec: 29 | replicas: 2 # Defines parallelism for the tasks 30 | serviceName: flink-taskmanager 31 | selector: 32 | matchLabels: 33 | app: flink 34 | component: taskmanager 35 | template: 36 | metadata: 37 | labels: 38 | app: flink 39 | component: taskmanager 40 | spec: 41 | initContainers: 42 | - name: dependencies 43 | image: waisbrot/wait 44 | imagePullPolicy: IfNotPresent 45 | env: 46 | - name: TARGETS 47 | value: flink-jobmanager.opennms.svc.cluster.local:6123 48 | - name: TIMEOUT 49 | value: '900' 50 | containers: 51 | - name: taskmanager 52 | image: apache/flink:1.13-java11 53 | imagePullPolicy: IfNotPresent 54 | args: 55 | - taskmanager 56 | env: 57 | - name: TZ 58 | valueFrom: 59 | configMapKeyRef: 60 | key: TIMEZONE 61 | name: common-settings 62 | - name: POD_NAME 63 | valueFrom: 64 | fieldRef: 65 | fieldPath: metadata.name 66 | - name: FLINK_PROPERTIES 67 | value: | 68 | jobmanager.rpc.address: flink-jobmanager.opennms.svc.cluster.local 69 | taskmanager.host: $(POD_NAME).flink-taskmanager.opennms.svc.cluster.local 70 | taskmanager.data.port: 6121 71 | taskmanager.rpc.port: 6122 72 | jobmanager.rpc.port: 6123 73 | blob.server.port: 6124 74 | queryable-state.proxy.ports: 6125 75 | taskmanager.numberOfTaskSlots: 2 76 | parallelism.default: 2 77 | ports: 78 | - containerPort: 6121 79 | name: data 80 | - containerPort: 6122 81 | name: rpc 82 | - containerPort: 6125 83 | name: query 84 | livenessProbe: 85 | tcpSocket: 86 | port: 6122 87 | initialDelaySeconds: 30 88 | periodSeconds: 60 89 | resources: 90 | limits: 91 | cpu: 400m 92 | memory: 2Gi 93 | 94 | -------------------------------------------------------------------------------- /manifests/grafana.helm.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # A one time Job to enable OpenNMS Helm and creating the data sources in Grafana 4 | 5 | --- 6 | apiVersion: batch/v1 7 | kind: Job 8 | metadata: 9 | name: helm-init 10 | namespace: opennms 11 | labels: 12 | app: grafana 13 | spec: 14 | ttlSecondsAfterFinished: 120 15 | template: 16 | spec: 17 | restartPolicy: Never 18 | containers: 19 | - name: init-config 20 | image: curlimages/curl 21 | imagePullPolicy: IfNotPresent 22 | command: 23 | - sh 24 | - /onms-helm-init.sh 25 | env: 26 | - name: TZ 27 | valueFrom: 28 | configMapKeyRef: 29 | key: TIMEZONE 30 | name: common-settings 31 | - name: DOMAIN 32 | valueFrom: 33 | configMapKeyRef: 34 | key: DOMAIN 35 | name: common-settings 36 | - name: GF_SECURITY_ADMIN_PASSWORD 37 | valueFrom: 38 | secretKeyRef: 39 | key: GRAFANA_UI_ADMIN_PASSWORD 40 | name: onms-passwords 41 | - name: GRAFANA_URL 42 | value: http://grafana.opennms.svc.cluster.local:3000 43 | - name: ONMS_URL 44 | value: https://onmsui.$(DOMAIN)/opennms 45 | - name: ONMS_USER 46 | value: admin 47 | - name: ONMS_PASSWD 48 | valueFrom: 49 | secretKeyRef: 50 | key: OPENNMS_UI_ADMIN_PASSWORD 51 | name: onms-passwords 52 | volumeMounts: 53 | - name: init-scripts 54 | mountPath: /onms-helm-init.sh 55 | subPath: onms-helm-init.sh 56 | volumes: 57 | - name: init-scripts 58 | configMap: 59 | name: init-scripts 60 | -------------------------------------------------------------------------------- /manifests/grafana.renderer.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: grafana-renderer 8 | namespace: opennms 9 | labels: 10 | app: grafana-renderer 11 | spec: 12 | ports: 13 | - port: 8081 14 | name: http 15 | selector: 16 | app: grafana-renderer 17 | 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: grafana-renderer 23 | namespace: opennms 24 | labels: 25 | app: grafana-renderer 26 | spec: 27 | replicas: 1 28 | selector: 29 | matchLabels: 30 | app: grafana-renderer 31 | template: 32 | metadata: 33 | labels: 34 | app: grafana-renderer 35 | spec: 36 | containers: 37 | - name: grafana-renderer 38 | image: grafana/grafana-image-renderer:latest 39 | imagePullPolicy: IfNotPresent 40 | ports: 41 | - containerPort: 8081 42 | name: http 43 | env: 44 | - name: BROWSER_TZ 45 | valueFrom: 46 | configMapKeyRef: 47 | key: TIMEZONE 48 | name: common-settings 49 | - name: ENABLE_METRICS 50 | value: 'true' 51 | resources: 52 | limits: 53 | cpu: 200m 54 | memory: 256Mi 55 | requests: 56 | cpu: 100m 57 | memory: 128Mi 58 | readinessProbe: 59 | httpGet: 60 | path: / 61 | port: http 62 | initialDelaySeconds: 10 63 | periodSeconds: 10 64 | livenessProbe: 65 | httpGet: 66 | path: / 67 | port: http 68 | initialDelaySeconds: 30 69 | periodSeconds: 60 70 | -------------------------------------------------------------------------------- /manifests/grafana.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # TODO 4 | # Consider switching to StatefulSet to guarantee zero collisions when creating the PG database. 5 | 6 | --- 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: grafana 11 | namespace: opennms 12 | labels: 13 | app: grafana 14 | spec: 15 | ports: 16 | - port: 3000 17 | name: http 18 | selector: 19 | app: grafana 20 | 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | name: grafana 26 | namespace: opennms 27 | labels: 28 | app: grafana 29 | spec: 30 | replicas: 2 31 | selector: 32 | matchLabels: 33 | app: grafana 34 | template: 35 | metadata: 36 | labels: 37 | app: grafana 38 | spec: 39 | initContainers: 40 | - name: dependencies 41 | image: waisbrot/wait 42 | imagePullPolicy: IfNotPresent 43 | env: 44 | - name: TARGETS 45 | value: postgresql.opennms.svc.cluster.local:5432 46 | - name: TIMEOUT 47 | value: '900' 48 | - name: init-database # Should run once (regardless of the replicas) 49 | image: postgres:13 50 | imagePullPolicy: IfNotPresent 51 | command: 52 | - sh 53 | - /bin/grafana-init.sh 54 | env: 55 | - name: PGHOST 56 | value: postgresql.opennms.svc.cluster.local 57 | - name: PGPORT 58 | value: '5432' 59 | - name: PGUSER 60 | value: postgres 61 | - name: PGPASSWORD 62 | valueFrom: 63 | secretKeyRef: 64 | key: POSTGRES_PASSWORD 65 | name: onms-passwords 66 | - name: GF_DATABASE_NAME 67 | value: grafana 68 | - name: GF_DATABASE_USER 69 | valueFrom: 70 | secretKeyRef: 71 | key: GRAFANA_DB_USERNAME 72 | name: onms-passwords 73 | - name: GF_DATABASE_PASSWORD 74 | valueFrom: 75 | secretKeyRef: 76 | key: GRAFANA_DB_PASSWORD 77 | name: onms-passwords 78 | volumeMounts: 79 | - name: init-scripts 80 | mountPath: /bin/grafana-init.sh 81 | subPath: grafana-init.sh 82 | containers: 83 | - name: grafana-helm 84 | image: opennms/helm:7.1.2 85 | imagePullPolicy: IfNotPresent 86 | ports: 87 | - containerPort: 3000 88 | name: http 89 | env: 90 | - name: TZ 91 | valueFrom: 92 | configMapKeyRef: 93 | key: TIMEZONE 94 | name: common-settings 95 | - name: DOMAIN 96 | valueFrom: 97 | configMapKeyRef: 98 | key: DOMAIN 99 | name: common-settings 100 | - name: GF_SECURITY_ADMIN_PASSWORD 101 | valueFrom: 102 | secretKeyRef: 103 | key: GRAFANA_UI_ADMIN_PASSWORD 104 | name: onms-passwords 105 | - name: GF_SERVER_DOMAIN 106 | value: grafana.$(DOMAIN) 107 | - name: GF_SERVER_ROOT_URL 108 | value: / 109 | - name: PGHOST 110 | value: postgresql.opennms.svc.cluster.local 111 | - name: PGPORT 112 | value: '5432' 113 | - name: GF_DATABASE_TYPE 114 | value: postgres 115 | - name: GF_DATABASE_SSL_MODE 116 | value: disable 117 | - name: GF_DATABASE_HOST 118 | value: $(PGHOST):$(PGPORT) 119 | - name: GF_DATABASE_NAME 120 | value: grafana 121 | - name: GF_DATABASE_USER 122 | valueFrom: 123 | secretKeyRef: 124 | key: GRAFANA_DB_USERNAME 125 | name: onms-passwords 126 | - name: GF_DATABASE_PASSWORD 127 | valueFrom: 128 | secretKeyRef: 129 | key: GRAFANA_DB_PASSWORD 130 | name: onms-passwords 131 | - name: GF_SESSION_PROVIDER 132 | value: postgres 133 | - name: GF_SESSION_PROVIDER_CONFIG 134 | value: dbname=$(GF_DATABASE_NAME) user=$(GF_DATABASE_USER) password=$(GF_DATABASE_PASSWORD) host=$(PGHOST) port=$(PGPORT) sslmode=$(GF_DATABASE_SSL_MODE) 135 | - name: GF_RENDERING_SERVER_URL 136 | value: http://grafana-renderer:8081/render 137 | - name: GF_RENDERING_CALLBACK_URL 138 | value: http://grafana:3000/ 139 | resources: 140 | limits: 141 | cpu: 200m 142 | memory: 256Mi 143 | requests: 144 | cpu: 100m 145 | memory: 128Mi 146 | readinessProbe: 147 | httpGet: 148 | path: / 149 | port: http 150 | initialDelaySeconds: 10 151 | periodSeconds: 10 152 | livenessProbe: 153 | httpGet: 154 | path: / 155 | port: http 156 | initialDelaySeconds: 30 157 | periodSeconds: 60 158 | volumes: 159 | - name: init-scripts 160 | configMap: 161 | name: init-scripts 162 | -------------------------------------------------------------------------------- /manifests/grpc-server.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # Source: https://github.com/agalue/onms-grpc-server/ 3 | # 4 | # WARNING: 5 | # - Connection is encrypted, but there is no authentication in place. 6 | # Any minion can connect to it, which is not ideal. 7 | 8 | --- 9 | apiVersion: v1 10 | kind: Service 11 | metadata: 12 | name: grpc-server 13 | namespace: opennms 14 | labels: 15 | app: grpc-server 16 | spec: 17 | ports: 18 | - port: 8990 19 | name: http 20 | - port: 2112 21 | name: prometheus 22 | selector: 23 | app: grpc-server 24 | 25 | --- 26 | apiVersion: apps/v1 27 | kind: Deployment 28 | metadata: 29 | name: grpc-server 30 | namespace: opennms 31 | labels: 32 | app: grpc-server 33 | spec: 34 | replicas: 2 35 | selector: 36 | matchLabels: 37 | app: grpc-server 38 | template: 39 | metadata: 40 | labels: 41 | app: grpc-server 42 | spec: 43 | initContainers: 44 | - name: dependencies 45 | image: waisbrot/wait 46 | imagePullPolicy: IfNotPresent 47 | env: 48 | - name: TARGETS 49 | value: kafka.opennms.svc.cluster.local:9092 50 | - name: TIMEOUT 51 | value: '900' 52 | containers: 53 | - name: grpc-server 54 | image: agalue/onms-grpc-server 55 | imagePullPolicy: Always 56 | ports: 57 | - containerPort: 8990 58 | name: http 59 | - containerPort: 2112 60 | name: prometheus 61 | env: 62 | - name: TZ 63 | valueFrom: 64 | configMapKeyRef: 65 | key: TIMEZONE 66 | name: common-settings 67 | - name: PORT 68 | value: '8990' 69 | - name: HTTP_PORT 70 | value: '2112' 71 | - name: BOOTSTRAP_SERVER 72 | value: kafka.opennms.svc.cluster.local:9092 73 | - name: TLS_ENABLED 74 | value: 'false' 75 | - name: INSTANCE_ID 76 | valueFrom: 77 | configMapKeyRef: 78 | key: OPENNMS_INSTANCE_ID 79 | name: common-settings 80 | - name: MAX_MESSAGE_SIZE 81 | value: '4194304' 82 | # Kafka Consumer 83 | - name: CONSUMER_SECURITY_PROTOCOL 84 | value: SASL_PLAINTEXT 85 | - name: CONSUMER_SASL_MECHANISM 86 | value: PLAIN 87 | - name: CONSUMER_SASL_USERNAME 88 | valueFrom: 89 | secretKeyRef: 90 | key: KAFKA_CLIENT_USER 91 | name: onms-passwords 92 | - name: CONSUMER_SASL_PASSWORD 93 | valueFrom: 94 | secretKeyRef: 95 | key: KAFKA_CLIENT_PASSWORD 96 | name: onms-passwords 97 | - name: CONSUMER_AUTO_OFFSET_RESET 98 | value: latest 99 | - name: CONSUMER_MAX_PARTITION_FETCH_BYTES 100 | value: '5000000' 101 | # Kafka Producer 102 | - name: PRODUCER_SECURITY_PROTOCOL 103 | value: SASL_PLAINTEXT 104 | - name: PRODUCER_SASL_MECHANISM 105 | value: PLAIN 106 | - name: PRODUCER_SASL_USERNAME 107 | valueFrom: 108 | secretKeyRef: 109 | key: KAFKA_CLIENT_USER 110 | name: onms-passwords 111 | - name: PRODUCER_SASL_PASSWORD 112 | valueFrom: 113 | secretKeyRef: 114 | key: KAFKA_CLIENT_PASSWORD 115 | name: onms-passwords 116 | - name: PRODUCER_MESSAGE_MAX_BYTES 117 | value: '5000000' 118 | resources: 119 | limits: 120 | cpu: 100m 121 | memory: 256Mi 122 | requests: 123 | cpu: 50m 124 | memory: 128Mi 125 | readinessProbe: 126 | exec: 127 | command: ['/bin/grpc_health_probe', '-addr', ':8990', '-rpc-timeout', '2s'] 128 | initialDelaySeconds: 20 129 | periodSeconds: 10 130 | timeoutSeconds: 3 131 | livenessProbe: 132 | exec: 133 | command: ['/bin/grpc_health_probe', '-addr', ':8990', '-rpc-timeout', '2s'] 134 | initialDelaySeconds: 30 135 | periodSeconds: 30 136 | timeoutSeconds: 3 137 | 138 | -------------------------------------------------------------------------------- /manifests/hasura.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # GraphQL API with a development UI for PostgreSQL databases. 4 | # This is an optional component 5 | # 6 | # Reference: 7 | # - https://github.com/hasura/graphql-engine/tree/master/install-manifests/kubernetes 8 | # - https://github.com/hasura/hasura-k8s-stack 9 | # 10 | # TODO: 11 | # - What about other env vars? https://docs.hasura.io/1.0/graphql/manual/deployment/graphql-engine-flags/reference.html 12 | # - Where the GraphQL schema is stored? Looks like within PostgreSQL itself. 13 | # - How to pass the GraphQL schema to the container? Find a way to export/import it. 14 | 15 | --- 16 | apiVersion: v1 17 | kind: ConfigMap 18 | metadata: 19 | name: hasura-config 20 | namespace: opennms 21 | labels: 22 | app: hasura 23 | data: 24 | hasura.sql: |+ 25 | CREATE USER hasurauser WITH PASSWORD 'hasurauser'; 26 | CREATE EXTENSION IF NOT EXISTS pgcrypto; 27 | CREATE SCHEMA IF NOT EXISTS hdb_catalog; 28 | CREATE SCHEMA IF NOT EXISTS hdb_views; 29 | ALTER SCHEMA hdb_catalog OWNER TO hasurauser; 30 | ALTER SCHEMA hdb_views OWNER TO hasurauser; 31 | GRANT SELECT ON ALL TABLES IN SCHEMA information_schema TO hasurauser; 32 | GRANT SELECT ON ALL TABLES IN SCHEMA pg_catalog TO hasurauser; 33 | GRANT ALL ON ALL TABLES IN SCHEMA public TO hasurauser; 34 | GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO hasurauser; 35 | 36 | --- 37 | apiVersion: v1 38 | kind: Service 39 | metadata: 40 | name: hasura 41 | namespace: opennms 42 | labels: 43 | app: hasura 44 | spec: 45 | ports: 46 | - port: 8080 47 | name: http 48 | selector: 49 | app: hasura 50 | 51 | --- 52 | apiVersion: apps/v1 53 | kind: Deployment 54 | metadata: 55 | name: hasura 56 | namespace: opennms 57 | labels: 58 | app: hasura 59 | spec: 60 | replicas: 1 61 | selector: 62 | matchLabels: 63 | app: hasura 64 | template: 65 | metadata: 66 | labels: 67 | app: hasura 68 | spec: 69 | initContainers: 70 | # Make sure that the OpenNMS database exist 71 | - name: dependencies 72 | image: waisbrot/wait 73 | imagePullPolicy: IfNotPresent 74 | env: 75 | - name: TARGETS 76 | value: opennms-core.opennms.svc.cluster.local:8980 77 | - name: TIMEOUT 78 | value: '900' 79 | # Patch the database for Hasura 80 | - name: init-config 81 | image: postgres:13 82 | imagePullPolicy: IfNotPresent 83 | command: 84 | - sh 85 | - -c 86 | - exec psql -f /hasura.sql 87 | env: 88 | - name: TZ 89 | valueFrom: 90 | configMapKeyRef: 91 | key: TIMEZONE 92 | name: common-settings 93 | - name: PGHOST 94 | value: postgresql.opennms.svc.cluster.local 95 | - name: PGPORT 96 | value: '5432' 97 | - name: PGUSER 98 | value: postgres 99 | - name: PGPASSWORD 100 | valueFrom: 101 | secretKeyRef: 102 | key: POSTGRES_PASSWORD 103 | name: onms-passwords 104 | volumeMounts: 105 | - name: hasura-config 106 | mountPath: /hasura.sql 107 | subPath: hasura.sql 108 | containers: 109 | - name: hasura 110 | image: hasura/graphql-engine:v1.3.2 111 | imagePullPolicy: IfNotPresent 112 | env: 113 | - name: PG_PASSWD 114 | valueFrom: 115 | secretKeyRef: 116 | key: POSTGRES_PASSWORD 117 | name: onms-passwords 118 | - name: HASURA_GRAPHQL_DATABASE_URL 119 | value: postgres://postgres:$(PG_PASSWD)@postgresql.opennms.svc.cluster.local:5432/opennms 120 | - name: HASURA_GRAPHQL_ACCESS_KEY 121 | valueFrom: 122 | secretKeyRef: 123 | key: HASURA_GRAPHQL_ACCESS_KEY 124 | name: onms-passwords 125 | - name: HASURA_GRAPHQL_ENABLE_CONSOLE 126 | value: 'true' 127 | ports: 128 | - containerPort: 8080 129 | name: http 130 | resources: 131 | limits: 132 | memory: 256Mi 133 | cpu: 200m 134 | requests: 135 | memory: 128Mi 136 | cpu: 50m 137 | readinessProbe: 138 | httpGet: 139 | path: / 140 | port: http 141 | initialDelaySeconds: 10 142 | periodSeconds: 10 143 | livenessProbe: 144 | httpGet: 145 | path: / 146 | port: http 147 | initialDelaySeconds: 30 148 | periodSeconds: 60 149 | volumes: 150 | - name: hasura-config 151 | configMap: 152 | name: hasura-config 153 | -------------------------------------------------------------------------------- /manifests/jaeger.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # This is an optional component 4 | # The following deploys an all-in-one instance for demo purposes. 5 | 6 | --- 7 | apiVersion: jaegertracing.io/v1 8 | kind: Jaeger 9 | metadata: 10 | name: onms-tracing # Agent will be available through onms-tracing-agent 11 | namespace: opennms 12 | spec: 13 | ingress: 14 | enabled: false 15 | -------------------------------------------------------------------------------- /manifests/kafka.manager.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # This is an optional component 4 | 5 | --- 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | name: kafka-manager 10 | namespace: opennms 11 | labels: 12 | app: kafka-manager 13 | spec: 14 | ports: 15 | - port: 9000 16 | name: http 17 | selector: 18 | app: kafka-manager 19 | 20 | --- 21 | apiVersion: apps/v1 22 | kind: Deployment 23 | metadata: 24 | name: kafka-manager 25 | namespace: opennms 26 | labels: 27 | app: kafka-manager 28 | spec: 29 | replicas: 1 30 | selector: 31 | matchLabels: 32 | app: kafka-manager 33 | template: 34 | metadata: 35 | labels: 36 | app: kafka-manager 37 | spec: 38 | initContainers: 39 | - name: dependencies 40 | image: waisbrot/wait 41 | imagePullPolicy: IfNotPresent 42 | env: 43 | - name: TARGETS 44 | value: kafka.opennms.svc.cluster.local:9092 45 | - name: TIMEOUT 46 | value: '900' 47 | containers: 48 | - name: kafka-manager 49 | image: hlebalbau/kafka-manager:stable 50 | imagePullPolicy: IfNotPresent 51 | env: 52 | - name: TZ 53 | valueFrom: 54 | configMapKeyRef: 55 | key: TIMEZONE 56 | name: common-settings 57 | - name: ZK_HOSTS 58 | value: zookeeper.opennms.svc.cluster.local:2181 59 | - name: KAFKA_MANAGER_AUTH_ENABLED 60 | value: 'true' 61 | - name: KAFKA_MANAGER_USERNAME 62 | valueFrom: 63 | secretKeyRef: 64 | key: KAFKA_MANAGER_USERNAME 65 | name: onms-passwords 66 | - name: KAFKA_MANAGER_PASSWORD 67 | valueFrom: 68 | secretKeyRef: 69 | key: KAFKA_MANAGER_PASSWORD 70 | name: onms-passwords 71 | - name: APPLICATION_SECRET 72 | valueFrom: 73 | secretKeyRef: 74 | key: KAFKA_MANAGER_APPLICATION_SECRET 75 | name: onms-passwords 76 | ports: 77 | - containerPort: 9000 78 | name: http 79 | resources: 80 | limits: 81 | memory: 512Mi 82 | cpu: 200m 83 | requests: 84 | memory: 256Mi 85 | cpu: 100m 86 | readinessProbe: 87 | tcpSocket: 88 | port: http 89 | initialDelaySeconds: 10 90 | periodSeconds: 10 91 | livenessProbe: 92 | tcpSocket: 93 | port: http 94 | initialDelaySeconds: 30 95 | periodSeconds: 60 -------------------------------------------------------------------------------- /manifests/kafka.producer.enhancer.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # Source: https://github.com/agalue/producer-enhancer 3 | # 4 | # This is an optional component 5 | # Enhance Alarms with Node data when available and send the result to a topic in JSON format 6 | 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: kafka-producer-enhancer 12 | namespace: opennms 13 | labels: 14 | app: kafka-producer-enhancer 15 | spec: 16 | replicas: 1 17 | selector: 18 | matchLabels: 19 | app: kafka-producer-enhancer 20 | template: 21 | metadata: 22 | labels: 23 | app: kafka-producer-enhancer 24 | spec: 25 | initContainers: 26 | - name: dependencies 27 | image: waisbrot/wait 28 | imagePullPolicy: IfNotPresent 29 | env: 30 | - name: TARGETS 31 | value: opennms-core.opennms.svc.cluster.local:8980 32 | - name: TIMEOUT 33 | value: '900' 34 | containers: 35 | - name: kafka-producer-enhancer 36 | image: agalue/producer-enhancer-go:latest 37 | imagePullPolicy: Always 38 | env: 39 | - name: INSTANCE_ID 40 | valueFrom: 41 | configMapKeyRef: 42 | key: OPENNMS_INSTANCE_ID 43 | name: common-settings 44 | - name: BOOTSTRAP_SERVER 45 | value: kafka.opennms.svc.cluster.local:9092 46 | - name: GROUP_ID 47 | value: alarms-enhancer-group 48 | - name: TARGET_KIND 49 | value: alarms 50 | - name: TARGET_TOPIC 51 | value: $(INSTANCE_ID)_enhanced_alarms 52 | - name: NODES_TOPIC # Check org.opennms.features.kafka.producer.cfg at the OpenNMS server 53 | value: $(INSTANCE_ID)_nodes 54 | - name: ALARMS_TOPIC # Check org.opennms.features.kafka.producer.cfg at the OpenNMS server 55 | value: $(INSTANCE_ID)_alarms 56 | resources: 57 | limits: 58 | memory: 128Mi 59 | cpu: 100m 60 | -------------------------------------------------------------------------------- /manifests/kafka.topics.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # A one time Job to create the required Kafka Topics 4 | 5 | --- 6 | apiVersion: batch/v1 7 | kind: Job 8 | metadata: 9 | name: kafka-init 10 | namespace: opennms 11 | labels: 12 | app: kafka 13 | spec: 14 | ttlSecondsAfterFinished: 120 15 | template: 16 | spec: 17 | restartPolicy: Never 18 | initContainers: 19 | # Wait for all the dependencies 20 | - name: dependencies 21 | image: waisbrot/wait 22 | imagePullPolicy: IfNotPresent 23 | env: 24 | - name: TARGETS 25 | value: kafka.opennms.svc.cluster.local:9092 26 | - name: TIMEOUT 27 | value: '900' 28 | containers: 29 | - name: init-config 30 | image: bitnami/kafka:2.8.1 31 | imagePullPolicy: IfNotPresent 32 | command: 33 | - bash 34 | - create-topics.sh 35 | env: 36 | - name: TZ 37 | valueFrom: 38 | configMapKeyRef: 39 | key: TIMEZONE 40 | name: common-settings 41 | - name: KAFKA_SERVER 42 | value: kafka.opennms.svc.cluster.local 43 | - name: KAFKA_CLIENT_USER 44 | valueFrom: 45 | secretKeyRef: 46 | key: KAFKA_CLIENT_USER 47 | name: onms-passwords 48 | - name: KAFKA_CLIENT_PASSWORD 49 | valueFrom: 50 | secretKeyRef: 51 | key: KAFKA_CLIENT_PASSWORD 52 | name: onms-passwords 53 | - name: INSTANCE_ID 54 | valueFrom: 55 | configMapKeyRef: 56 | key: OPENNMS_INSTANCE_ID 57 | name: common-settings 58 | - name: CREATE_TOPICS # Must match ALEC, Nephron and OpenNMS settings 59 | value: $(INSTANCE_ID)_nodes $(INSTANCE_ID)_alarms $(INSTANCE_ID)_alarms_feedback $(INSTANCE_ID)_alec_inventory $(INSTANCE_ID)_edges $(INSTANCE_ID)_opennms_flows 60 | - name: KAFKA_CFG_DEFAULT_REPLICATION_FACTOR 61 | valueFrom: # Must be consistent with the cluster size 62 | configMapKeyRef: 63 | key: KAFKA_REPLICATION_FACTOR 64 | name: common-settings 65 | - name: KAFKA_CFG_NUM_PARTITIONS # Must be greater than the number of Minions per location 66 | valueFrom: # Must be consistent with the chosen amount of replicas 67 | configMapKeyRef: 68 | key: KAFKA_NUM_PARTITIONS 69 | name: common-settings 70 | volumeMounts: 71 | - name: init-scripts 72 | mountPath: /create-topics.sh 73 | subPath: create-topics.sh 74 | volumes: 75 | - name: init-scripts 76 | configMap: 77 | name: init-scripts 78 | -------------------------------------------------------------------------------- /manifests/kibana.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # WARNING: 4 | # - The kibana user (uid: 1000, gid: 1000) cannot be used in OpenShift by default. 5 | 6 | --- 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: kibana 11 | namespace: opennms 12 | labels: 13 | app: kibana 14 | spec: 15 | ports: 16 | - port: 5601 17 | name: http 18 | selector: 19 | app: kibana 20 | 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | name: kibana 26 | namespace: opennms 27 | labels: 28 | app: kibana 29 | spec: 30 | replicas: 1 31 | selector: 32 | matchLabels: 33 | app: kibana 34 | template: 35 | metadata: 36 | labels: 37 | app: kibana 38 | spec: 39 | initContainers: 40 | - name: dependencies 41 | image: waisbrot/wait 42 | imagePullPolicy: IfNotPresent 43 | env: 44 | - name: TARGETS 45 | value: esdata.opennms.svc.cluster.local:9200 46 | - name: TIMEOUT 47 | value: '900' 48 | containers: 49 | - name: kibana 50 | image: docker.elastic.co/kibana/kibana:7.6.2 51 | imagePullPolicy: IfNotPresent 52 | env: 53 | - name: TZ 54 | valueFrom: 55 | configMapKeyRef: 56 | key: TIMEZONE 57 | name: common-settings 58 | - name: SERVER_NAME 59 | valueFrom: 60 | fieldRef: 61 | fieldPath: metadata.name 62 | - name: ELASTICSEARCH_HOSTS 63 | value: http://esdata.opennms.svc.cluster.local:9200 64 | - name: ELASTICSEARCH_USERNAME 65 | value: elastic 66 | - name: ELASTICSEARCH_PASSWORD 67 | valueFrom: 68 | secretKeyRef: 69 | key: ELASTICSEARCH_PASSWORD 70 | name: onms-passwords 71 | ports: 72 | - containerPort: 5601 73 | name: http 74 | resources: 75 | limits: 76 | cpu: 200m 77 | memory: 1Gi 78 | requests: 79 | cpu: 100m 80 | memory: 512Mi 81 | readinessProbe: 82 | tcpSocket: 83 | port: http 84 | initialDelaySeconds: 30 85 | periodSeconds: 30 86 | livenessProbe: 87 | tcpSocket: 88 | port: http 89 | initialDelaySeconds: 60 90 | periodSeconds: 60 91 | -------------------------------------------------------------------------------- /manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | namespace: opennms 5 | 6 | resources: 7 | - namespace.yaml 8 | - postgresql.yaml 9 | - zookeeper.yaml 10 | - kafka.yaml 11 | - kafka.topics.yaml 12 | - kafka.manager.yaml 13 | - kafka.producer.enhancer.yaml 14 | - cassandra.yaml 15 | - cassandra.reaper.yaml 16 | - elasticsearch.master.yaml 17 | - elasticsearch.data.yaml 18 | - elasticsearch.curator.yaml 19 | - elastic-hq.yaml 20 | - kibana.yaml 21 | - flink.job-manager.yaml 22 | - flink.task-manager.yaml 23 | - opennms.core.yaml 24 | - opennms.minion.yaml 25 | - opennms.nephron.yaml 26 | - opennms.sentinel.yaml 27 | - opennms.alec.yaml 28 | - opennms.ui.yaml 29 | - grafana.yaml 30 | - grafana.renderer.yaml 31 | - grafana.helm.yaml 32 | - grpc-server.yaml 33 | - hasura.yaml 34 | - jaeger.yaml 35 | - cert-manager.yaml 36 | - external-access.yaml 37 | - event-watcher.yaml 38 | 39 | configMapGenerator: 40 | - name: init-scripts 41 | files: 42 | - config/create-topics.sh 43 | - config/grafana-init.sh 44 | - config/onms-helm-init.sh 45 | - config/onms-alec-init.sh 46 | - config/onms-core-init.sh 47 | - config/onms-minion-init.sh 48 | - config/onms-nephron-init.sh 49 | - config/onms-sentinel-init.sh 50 | - config/onms-ui-init.sh 51 | - name: common-settings 52 | literals: 53 | - DOMAIN=aws.agalue.net 54 | - TIMEZONE=America/New_York 55 | - OPENNMS_INSTANCE_ID=K8S 56 | - CASSANDRA_CLUSTER_NAME=OpenNMS 57 | - CASSANDRA_DC=Main 58 | - CASSANDRA_REPLICATION_FACTOR=2 59 | - ELASTIC_INDEX_STRATEGY_FLOWS=daily 60 | - ELASTIC_REPLICATION_FACTOR=2 61 | - ELASTIC_NUM_SHARDS=6 62 | - KAFKA_NUM_PARTITIONS=6 63 | - KAFKA_REPLICATION_FACTOR=2 64 | - MINION_LOCATION=Kubernetes 65 | 66 | secretGenerator: 67 | - name: onms-passwords 68 | literals: 69 | - POSTGRES_PASSWORD=postgres 70 | - OPENNMS_DB_PASSWORD=opennms 71 | - OPENNMS_UI_ADMIN_PASSWORD=admin 72 | - GRAFANA_UI_ADMIN_PASSWORD=0p3nNMS 73 | - GRAFANA_DB_USERNAME=grafana 74 | - GRAFANA_DB_PASSWORD=grafana 75 | - ELASTICSEARCH_USERNAME=elastic 76 | - ELASTICSEARCH_PASSWORD=elastic 77 | - KAFKA_MANAGER_APPLICATION_SECRET=0p3nNMS 78 | - KAFKA_MANAGER_USERNAME=opennms 79 | - KAFKA_MANAGER_PASSWORD=0p3nNMS 80 | - HASURA_GRAPHQL_ACCESS_KEY=0p3nNMS 81 | - KAFKA_CLIENT_USER=opennms 82 | - KAFKA_CLIENT_PASSWORD=0p3nNM5 83 | - KAFKA_INTER_BROKER_USER=kafka 84 | - KAFKA_INTER_BROKER_PASSWORD=K@fk@ 85 | 86 | generatorOptions: 87 | disableNameSuffixHash: true 88 | labels: 89 | target: opennms 90 | annotations: 91 | owner: agalue@opennms.org 92 | -------------------------------------------------------------------------------- /manifests/namespace.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # TODO Define resource quota and limits. 4 | # TODO Define default limits. 5 | # 6 | # Adding Users: 7 | # 8 | # - Create a CSR with openssl or cfssl. 9 | # - Create a CertificateSigningRequest object. 10 | # - Approve the certificate using the "kubectl certificate approve" command. 11 | # - Download the signed certificate using the "kubectl get csr" command. 12 | # - Set the kubectl context with the key and the certificate. 13 | # 14 | # To create user 'agalue' that belongs to the group 'onms-admin': 15 | # 16 | # cat < agalue.crt 65 | # 66 | # Then add a new context for user 'agalue': 67 | # 68 | # cluster=$(kubectl config view -o jsonpath="{.contexts[?(@.name=='$(kubectl config current-context)')].context.cluster}") 69 | # kubectl config set-credentials agalue --client-certificate=agalue.crt --client-key=agalue-key.pem 70 | # kubectl config set-context agalue-ctx --user=agalue --namespace=opennms --cluster=$cluster 71 | # 72 | # Then, switch to the new context for user 'agalue': 73 | # kubectl config use-context agalue-ctx 74 | # 75 | # You can repeat the same process for user 'jdoe'. 76 | 77 | --- 78 | apiVersion: v1 79 | kind: Namespace 80 | metadata: 81 | name: opennms 82 | 83 | # The following is optional. 84 | # It is here as a template in case other admin/operators have to interact with the cluster. 85 | 86 | # RBAC Role to view certain object in the opennms namespace. 87 | --- 88 | kind: Role 89 | apiVersion: rbac.authorization.k8s.io/v1 90 | metadata: 91 | namespace: opennms 92 | name: opennms-operators 93 | rules: 94 | - apiGroups: 95 | - "" 96 | resources: 97 | - configmaps 98 | - pods 99 | - pods/log 100 | - services 101 | verbs: 102 | - get 103 | - list 104 | - watch 105 | 106 | # RBAC RoleBinding to associate the users of group "onms-operator" to the "opennms-operators" role. 107 | --- 108 | kind: RoleBinding 109 | apiVersion: rbac.authorization.k8s.io/v1 110 | metadata: 111 | namespace: opennms 112 | name: opennms-operators-binding 113 | subjects: 114 | - kind: Group 115 | name: onms-operator 116 | apiGroup: rbac.authorization.k8s.io 117 | roleRef: 118 | kind: Role 119 | name: opennms-operators 120 | apiGroup: rbac.authorization.k8s.io 121 | 122 | # RBAC Role to manage any object in the opennms namespace. 123 | --- 124 | kind: Role 125 | apiVersion: rbac.authorization.k8s.io/v1 126 | metadata: 127 | namespace: opennms 128 | name: opennms-admins 129 | rules: 130 | - apiGroups: 131 | - "*" 132 | resources: 133 | - "*" 134 | verbs: 135 | - "*" 136 | 137 | # RBAC RoleBinding to associate the users of group "onms-admin" to the "opennms-admins" role. 138 | --- 139 | kind: RoleBinding 140 | apiVersion: rbac.authorization.k8s.io/v1 141 | metadata: 142 | namespace: opennms 143 | name: opennms-admins-binding 144 | subjects: 145 | - kind: Group 146 | name: onms-admin 147 | apiGroup: rbac.authorization.k8s.io 148 | roleRef: 149 | kind: Role 150 | name: opennms-admins 151 | apiGroup: rbac.authorization.k8s.io 152 | 153 | # RBAC ClusterRoleBinding to allow users of group "onms-admin" to manage persistent volumes. 154 | # In theory, the following is not mandatory, and for security purposes, should be avoided. 155 | --- 156 | kind: ClusterRoleBinding 157 | apiVersion: rbac.authorization.k8s.io/v1 158 | metadata: 159 | namespace: opennms 160 | name: opennms-storage-binding 161 | subjects: 162 | - kind: Group 163 | name: onms-admin 164 | apiGroup: rbac.authorization.k8s.io 165 | roleRef: 166 | kind: ClusterRole 167 | name: system:persistent-volume-provisioner 168 | apiGroup: rbac.authorization.k8s.io 169 | -------------------------------------------------------------------------------- /manifests/opennms.alec.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # WARNING: 4 | # - Internet access from the container is required to retrieve ALEC binaries from GitHub. 5 | 6 | --- 7 | apiVersion: apps/v1 8 | kind: StatefulSet 9 | metadata: 10 | name: alec 11 | namespace: opennms 12 | labels: 13 | app: alec 14 | spec: 15 | serviceName: alec 16 | replicas: 2 17 | selector: 18 | matchLabels: 19 | app: alec 20 | template: 21 | metadata: 22 | labels: 23 | app: alec 24 | spec: 25 | terminationGracePeriodSeconds: 60 26 | affinity: # Avoid scheduling the pod in the same host as the Core OpenNMS when possible 27 | podAntiAffinity: 28 | preferredDuringSchedulingIgnoredDuringExecution: 29 | - weight: 100 30 | podAffinityTerm: 31 | topologyKey: kubernetes.io/hostname 32 | labelSelector: 33 | matchExpressions: 34 | - key: app 35 | operator: In 36 | values: 37 | - onms 38 | initContainers: 39 | # Wait for all the dependencies 40 | - name: dependencies 41 | image: waisbrot/wait 42 | imagePullPolicy: IfNotPresent 43 | env: 44 | - name: TARGETS 45 | value: opennms-core.opennms.svc.cluster.local:8980 46 | - name: TIMEOUT 47 | value: '900' 48 | # Initialize Sentinel Configuration for ALEC in Distributed Mode 49 | # Requires the same image/version used at runtime: sentinel 50 | - name: init-config 51 | image: bash 52 | imagePullPolicy: IfNotPresent 53 | command: [ bash, /init.sh ] 54 | env: 55 | - name: TZ 56 | valueFrom: 57 | configMapKeyRef: 58 | key: TIMEZONE 59 | name: common-settings 60 | - name: INSTANCE_ID 61 | valueFrom: 62 | configMapKeyRef: 63 | key: OPENNMS_INSTANCE_ID 64 | name: common-settings 65 | - name: KAFKA_SERVER 66 | value: kafka.opennms.svc.cluster.local 67 | - name: KAFKA_SASL_USERNAME 68 | valueFrom: 69 | secretKeyRef: 70 | key: KAFKA_CLIENT_USER 71 | name: onms-passwords 72 | - name: KAFKA_SASL_PASSWORD 73 | valueFrom: 74 | secretKeyRef: 75 | key: KAFKA_CLIENT_PASSWORD 76 | name: onms-passwords 77 | - name: ZOOKEEPER_SERVER 78 | value: zookeeper.opennms.svc.cluster.local 79 | volumeMounts: 80 | - name: etc-overlay 81 | mountPath: /etc-overlay 82 | - name: init-scripts 83 | mountPath: /init.sh 84 | subPath: onms-alec-init.sh 85 | # Install ALEC assuming it is not present on the Sentinel Docker Image 86 | - name: alec-plugin 87 | image: busybox 88 | command: [ sh, -c ] 89 | args: [ "cd /plugin && wget -q -nc --no-check-certificate $ALEC_KAR_URL" ] 90 | env: 91 | - name: ALEC_KAR_URL 92 | value: https://github.com/OpenNMS/alec/releases/download/v1.1.1/opennms-alec-plugin.kar 93 | volumeMounts: 94 | - name: karaf-deploy 95 | mountPath: /plugin 96 | containers: 97 | - name: alec 98 | image: opennms/sentinel:27.2.0 # Due to https://issues.opennms.org/browse/NMS-13664 99 | imagePullPolicy: IfNotPresent 100 | args: 101 | - -c 102 | ports: 103 | - containerPort: 8181 104 | name: http 105 | - containerPort: 8301 106 | name: karaf 107 | env: 108 | - name: TZ 109 | valueFrom: 110 | configMapKeyRef: 111 | key: TIMEZONE 112 | name: common-settings 113 | - name: OPENNMS_HTTP_URL 114 | value: http://opennms-core.opennms.svc.cluster.local:8980/opennms 115 | - name: OPENNMS_HTTP_USER 116 | value: admin 117 | - name: OPENNMS_HTTP_PASS 118 | valueFrom: 119 | secretKeyRef: 120 | key: OPENNMS_UI_ADMIN_PASSWORD 121 | name: onms-passwords 122 | - name: MEM_TOTAL_MB 123 | valueFrom: 124 | resourceFieldRef: 125 | resource: requests.memory 126 | divisor: 1Mi 127 | - name: JAVA_OPTS 128 | value: -Xms$(MEM_TOTAL_MB)m -Xmx$(MEM_TOTAL_MB)m -XX:+AlwaysPreTouch -XX:+UseG1GC -XX:+UseStringDeduplication 129 | volumeMounts: 130 | - name: etc-overlay 131 | mountPath: /opt/sentinel-etc-overlay 132 | - name: karaf-deploy 133 | mountPath: /opt/sentinel/deploy 134 | resources: 135 | limits: 136 | cpu: 750m 137 | memory: 3Gi 138 | requests: 139 | cpu: 500m 140 | memory: 2Gi 141 | readinessProbe: 142 | exec: 143 | command: 144 | - /health.sh 145 | initialDelaySeconds: 60 146 | periodSeconds: 15 147 | livenessProbe: # WARNING: The health-check checks dependencies, which is not suitable for liveness probes 148 | exec: 149 | command: 150 | - /health.sh 151 | initialDelaySeconds: 60 152 | periodSeconds: 60 153 | timeoutSeconds: 15 154 | volumes: 155 | - name: etc-overlay 156 | emptyDir: {} 157 | - name: karaf-deploy 158 | emptyDir: {} 159 | - name: init-scripts 160 | configMap: 161 | name: init-scripts 162 | -------------------------------------------------------------------------------- /manifests/opennms.nephron.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # Starts Nephron as a Flink Job, using a custom Flink image containing the Nephron JAR bundle within it. 4 | 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: nephron 10 | namespace: opennms 11 | labels: 12 | app: nephron 13 | spec: 14 | replicas: 1 # Do not change it. Use parallelism and task managers 15 | selector: 16 | matchLabels: 17 | app: nephron 18 | template: 19 | metadata: 20 | labels: 21 | app: nephron 22 | spec: 23 | initContainers: 24 | - name: init-config 25 | image: busybox 26 | imagePullPolicy: IfNotPresent 27 | env: 28 | - name: TZ 29 | valueFrom: 30 | configMapKeyRef: 31 | key: TIMEZONE 32 | name: common-settings 33 | - name: KAFKA_SASL_USERNAME 34 | valueFrom: 35 | secretKeyRef: 36 | key: KAFKA_CLIENT_USER 37 | name: onms-passwords 38 | - name: KAFKA_SASL_PASSWORD 39 | valueFrom: 40 | secretKeyRef: 41 | key: KAFKA_CLIENT_PASSWORD 42 | name: onms-passwords 43 | command: [ /bin/sh, /init.sh ] 44 | volumeMounts: 45 | - name: data 46 | mountPath: /data 47 | - name: init-scripts 48 | mountPath: /init.sh 49 | subPath: onms-nephron-init.sh 50 | - name: dependencies 51 | image: waisbrot/wait 52 | imagePullPolicy: IfNotPresent 53 | env: 54 | - name: TARGETS 55 | value: kafka.opennms.svc.cluster.local:9092,esdata.opennms.svc.cluster.local:9200,flink-jobmanager.opennms.svc.cluster.local:6123 56 | - name: TIMEOUT 57 | value: '900' 58 | containers: 59 | - name: nephron 60 | image: agalue/nephron:0.3.0 61 | imagePullPolicy: IfNotPresent 62 | args: 63 | - flink 64 | - run 65 | - --parallelism 66 | - '2' # Should be less or equal to the number of Task managers 67 | - --class 68 | - org.opennms.nephron.Nephron 69 | - /data/nephron-flink-bundled.jar 70 | - --runner=FlinkRunner 71 | - --jobName=nephron 72 | - --bootstrapServers=kafka.opennms.svc.cluster.local:9092 73 | - --kafkaClientProperties=/data/client.properties 74 | - --groupId=Nephron 75 | - --flowSourceTopic=$(INSTANCE_ID)_opennms_flows 76 | - --flowDestTopic=$(INSTANCE_ID)_opennms_flows_agg 77 | - --elasticUrl=http://esdata.opennms.svc.cluster.local:9200 78 | - --elasticFlowIndex=netflow_agg 79 | env: 80 | - name: TZ 81 | valueFrom: 82 | configMapKeyRef: 83 | key: TIMEZONE 84 | name: common-settings 85 | - name: INSTANCE_ID 86 | valueFrom: 87 | configMapKeyRef: 88 | key: OPENNMS_INSTANCE_ID 89 | name: common-settings 90 | - name: JOB_MANAGER_RPC_ADDRESS 91 | value: flink-jobmanager.opennms.svc.cluster.local 92 | volumeMounts: 93 | - name: data 94 | mountPath: /data 95 | resources: 96 | limits: 97 | cpu: 200m 98 | memory: 1Gi 99 | volumes: 100 | - name: data 101 | emptyDir: {} 102 | - name: init-scripts 103 | configMap: 104 | name: init-scripts 105 | -------------------------------------------------------------------------------- /manifests/postgresql.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # WARNING: 4 | # - PostgreSQL expects to have very fast disks for the PVC to avoid performance issues. 5 | # - Make sure to tune the max_connections to accomodate ONMS, Sentinels servers, UI servers and Grafana. 6 | # - Make sure to tune the rest of the settings based on https://pgtune.leopard.in.ua/#/ 7 | # - The postgres user (uid: 999, gid: 999) cannot be used in OpenShift by default. 8 | 9 | --- 10 | apiVersion: v1 11 | kind: ConfigMap 12 | metadata: 13 | name: postgresql-config 14 | namespace: opennms 15 | labels: 16 | app: postgresql 17 | data: 18 | # Based on https://pgtune.leopard.in.ua/#/ for the configured requested limits 19 | postgresql.conf: |+ 20 | listen_addresses = '*' 21 | shared_buffers = 512MB 22 | max_connections = 300 23 | effective_cache_size = 1536MB 24 | maintenance_work_mem = 128MB 25 | checkpoint_completion_target = 0.9 26 | wal_buffers = 16MB 27 | default_statistics_target = 100 28 | random_page_cost = 1.1 29 | effective_io_concurrency = 300 30 | work_mem = 873kB 31 | min_wal_size = 1GB 32 | max_wal_size = 2GB 33 | max_worker_processes = 2 34 | max_parallel_workers_per_gather = 1 35 | max_parallel_workers = 2 36 | 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | name: postgresql 42 | namespace: opennms 43 | labels: 44 | app: postgres 45 | spec: 46 | clusterIP: None 47 | ports: 48 | - port: 5432 49 | selector: 50 | app: postgres 51 | 52 | --- 53 | apiVersion: apps/v1 54 | kind: StatefulSet 55 | metadata: 56 | name: postgres 57 | namespace: opennms 58 | labels: 59 | app: postgres 60 | role: master 61 | spec: 62 | serviceName: postgresql 63 | replicas: 1 # The solution only allows 1 instance 64 | selector: 65 | matchLabels: 66 | app: postgres 67 | template: 68 | metadata: 69 | labels: 70 | app: postgres 71 | role: master 72 | spec: 73 | containers: 74 | - name: postgres 75 | image: postgres:13 76 | imagePullPolicy: IfNotPresent 77 | args: 78 | - postgres 79 | - -c 80 | - 'config_file=/etc/postgresql.conf' 81 | env: 82 | - name: TZ 83 | valueFrom: 84 | configMapKeyRef: 85 | key: TIMEZONE 86 | name: common-settings 87 | - name: POSTGRES_USER 88 | value: postgres 89 | - name: POSTGRES_PASSWORD 90 | valueFrom: 91 | secretKeyRef: 92 | key: POSTGRES_PASSWORD 93 | name: onms-passwords 94 | - name: PGDATA 95 | value: /var/lib/postgresql/data/pgdata 96 | ports: 97 | - containerPort: 5432 98 | name: pg 99 | volumeMounts: 100 | - name: data 101 | mountPath: /var/lib/postgresql/data 102 | - name: postgresql-config 103 | mountPath: /etc/postgresql.conf 104 | subPath: postgresql.conf 105 | resources: 106 | limits: 107 | cpu: '4' 108 | memory: 4Gi 109 | requests: 110 | cpu: '2' 111 | memory: 2Gi 112 | readinessProbe: 113 | exec: 114 | command: 115 | - sh 116 | - -c 117 | - exec pg_isready --host $HOSTNAME 118 | initialDelaySeconds: 10 119 | periodSeconds: 10 120 | livenessProbe: 121 | exec: 122 | command: 123 | - sh 124 | - -c 125 | - exec pg_isready --host $HOSTNAME 126 | initialDelaySeconds: 30 127 | periodSeconds: 60 128 | volumes: 129 | - name: postgresql-config 130 | configMap: 131 | name: postgresql-config 132 | volumeClaimTemplates: 133 | - metadata: 134 | name: data 135 | spec: 136 | accessModes: 137 | - ReadWriteOnce 138 | resources: 139 | requests: 140 | storage: 20Gi 141 | -------------------------------------------------------------------------------- /manifests/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # WARNING: 4 | # - Full FQDNs are required for ZOO_SERVERS; otherwise, the leader election fails. 5 | # - The zookeeper user (uid: 1000, gid: 1000) cannot be used in OpenShift by default. 6 | 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: zookeeper 11 | namespace: opennms 12 | labels: 13 | app: zk 14 | spec: 15 | ports: 16 | - port: 2181 17 | name: client 18 | - port: 2888 19 | name: server 20 | - port: 3888 21 | name: leader-election 22 | - port: 9998 23 | name: jmx 24 | clusterIP: None 25 | selector: 26 | app: zk 27 | 28 | --- 29 | apiVersion: policy/v1 30 | kind: PodDisruptionBudget 31 | metadata: 32 | name: zk-pdb 33 | spec: 34 | maxUnavailable: 1 35 | selector: 36 | matchLabels: 37 | app: zk 38 | 39 | --- 40 | apiVersion: apps/v1 41 | kind: StatefulSet 42 | metadata: 43 | name: zk 44 | namespace: opennms 45 | labels: 46 | app: zk 47 | spec: 48 | serviceName: zookeeper 49 | replicas: 3 # The solution is designed for 3 instances 50 | updateStrategy: 51 | type: RollingUpdate 52 | podManagementPolicy: OrderedReady 53 | selector: 54 | matchLabels: 55 | app: zk 56 | template: 57 | metadata: 58 | labels: 59 | app: zk 60 | spec: 61 | terminationGracePeriodSeconds: 300 62 | securityContext: 63 | runAsUser: 1000 64 | fsGroup: 1000 65 | initContainers: 66 | - name: generate-zooid 67 | image: busybox 68 | command: 69 | - sh 70 | - -c 71 | - ORD=${HOSTNAME##*-}; MYID=$((ORD+1)); echo $MYID > /data/myid 72 | volumeMounts: 73 | - name: data 74 | mountPath: /data 75 | containers: 76 | - name: zk 77 | image: zookeeper:3.5 78 | imagePullPolicy: IfNotPresent 79 | env: 80 | - name: TZ 81 | valueFrom: 82 | configMapKeyRef: 83 | key: TIMEZONE 84 | name: common-settings 85 | - name: ZOO_SERVERS # Must be consistent with the replicas 86 | value: server.1=zk-0.zookeeper.opennms.svc.cluster.local:2888:3888;2181 server.2=zk-1.zookeeper.opennms.svc.cluster.local:2888:3888;2181 server.3=zk-2.zookeeper.opennms.svc.cluster.local:2888:3888;2181 87 | - name: ZOO_STANDALONE_ENABLED 88 | value: 'false' 89 | - name: ZOO_4LW_COMMANDS_WHITELIST 90 | value: '*' 91 | - name: ZOO_TICK_TIME 92 | value: '2000' 93 | - name: ZOO_INIT_LIMIT 94 | value: '10' 95 | - name: ZOO_SYNC_LIMIT 96 | value: '5' 97 | - name: JMXLOCALONLY 98 | value: 'false' 99 | - name: JMXDISABLE 100 | value: 'false' 101 | - name: JMXPORT 102 | value: '9998' 103 | - name: JMXAUTH 104 | value: 'false' 105 | - name: JMXSSL 106 | value: 'false' 107 | - name: MEM_TOTAL_MB 108 | valueFrom: 109 | resourceFieldRef: 110 | resource: requests.memory 111 | divisor: 1Mi 112 | - name: JVMFLAGS 113 | value: -Xms$(MEM_TOTAL_MB)m -Xmx$(MEM_TOTAL_MB)m 114 | ports: 115 | - containerPort: 2181 116 | name: client 117 | - containerPort: 2888 118 | name: server 119 | - containerPort: 3888 120 | name: leader-election 121 | - containerPort: 8080 122 | name: admin 123 | - containerPort: 9998 124 | name: jmx 125 | volumeMounts: 126 | - name: data 127 | mountPath: /data 128 | resources: 129 | limits: 130 | memory: 512Mi 131 | cpu: 200m 132 | requests: 133 | memory: 256Mi 134 | cpu: 100m 135 | readinessProbe: 136 | exec: 137 | command: 138 | - sh 139 | - -c 140 | - '[ "imok" = "$(echo ruok | nc 127.0.0.1 2181)" ]' 141 | initialDelaySeconds: 10 142 | periodSeconds: 10 143 | livenessProbe: 144 | tcpSocket: 145 | port: client 146 | initialDelaySeconds: 30 147 | periodSeconds: 60 148 | volumeClaimTemplates: 149 | - metadata: 150 | name: data 151 | spec: 152 | accessModes: 153 | - ReadWriteOnce 154 | resources: 155 | requests: 156 | storage: 4Gi 157 | 158 | -------------------------------------------------------------------------------- /minikube/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | bases: 5 | - ../reduced 6 | 7 | patchesStrategicMerge: 8 | - patches/common-settings.yaml 9 | - patches/external-access.yaml 10 | - patches/opennms.core.yaml 11 | -------------------------------------------------------------------------------- /minikube/patches/common-settings.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: common-settings 8 | namespace: opennms 9 | data: 10 | DOMAIN: test 11 | TIMEZONE: America/New_York 12 | OPENNMS_INSTANCE_ID: K8S 13 | CASSANDRA_CLUSTER_NAME: OpenNMS 14 | CASSANDRA_DC: Main 15 | CASSANDRA_REPLICATION_FACTOR: "1" 16 | ELASTIC_INDEX_STRATEGY_FLOWS: daily 17 | ELASTIC_REPLICATION_FACTOR: "0" 18 | ELASTIC_NUM_SHARDS: "1" 19 | KAFKA_NUM_PARTITIONS: "1" 20 | KAFKA_REPLICATION_FACTOR: "1" 21 | MINION_LOCATION: Kubernetes 22 | -------------------------------------------------------------------------------- /minikube/patches/external-access.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | # 3 | # Requires the ingress-dns add-on in Minikube, and some configuration in your host machine 4 | 5 | --- 6 | apiVersion: networking.k8s.io/v1 7 | kind: Ingress 8 | metadata: 9 | name: onms-ingress 10 | namespace: opennms 11 | annotations: 12 | cert-manager.io/issuer: onms-ca-issuer 13 | spec: 14 | tls: 15 | - secretName: opennms-ingress-cert 16 | hosts: 17 | - onms.test 18 | - grafana.test 19 | - kafka-manager.test 20 | - kibana.test 21 | - tracing.test 22 | rules: 23 | - host: onms.test 24 | http: 25 | paths: 26 | - path: / 27 | pathType: Prefix 28 | backend: 29 | service: 30 | name: opennms-core 31 | port: 32 | number: 8980 33 | - host: grafana.test 34 | http: 35 | paths: 36 | - path: / 37 | pathType: Prefix 38 | backend: 39 | service: 40 | name: grafana 41 | port: 42 | number: 3000 43 | - host: kafka-manager.test 44 | http: 45 | paths: 46 | - path: / 47 | pathType: Prefix 48 | backend: 49 | service: 50 | name: kafka-manager 51 | port: 52 | number: 9000 53 | - host: kibana.test 54 | http: 55 | paths: 56 | - path: / 57 | pathType: Prefix 58 | backend: 59 | service: 60 | name: kibana 61 | port: 62 | number: 5601 63 | - host: tracing.test 64 | http: 65 | paths: 66 | - path: / 67 | pathType: Prefix 68 | backend: 69 | service: 70 | name: onms-tracing-query 71 | port: 72 | number: 16686 73 | 74 | --- 75 | apiVersion: networking.k8s.io/v1 76 | kind: Ingress 77 | metadata: 78 | name: grpc-ingress 79 | namespace: opennms 80 | annotations: 81 | cert-manager.io/issuer: onms-ca-issuer 82 | spec: 83 | tls: 84 | - secretName: grpc-ingress-cert 85 | hosts: 86 | - grpc.test 87 | rules: 88 | - host: grpc.test 89 | http: 90 | paths: 91 | - path: / 92 | pathType: Prefix 93 | backend: 94 | service: 95 | name: grpc-server 96 | port: 97 | number: 8990 98 | -------------------------------------------------------------------------------- /minikube/patches/opennms.core.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: onms 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | initContainers: 13 | - name: init-config 14 | securityContext: 15 | runAsNonRoot: false 16 | runAsUser: 0 17 | -------------------------------------------------------------------------------- /minion.yaml: -------------------------------------------------------------------------------- 1 | # WARNING: Make sure to update the domain on each host. 2 | 3 | # Basic Settings 4 | http-url: "https://onms.aws.agalue.net/opennms" 5 | id: "durham-minion-01" 6 | location: "Durham" 7 | 8 | # System Level Properties 9 | system: 10 | properties: 11 | # Instance ID (must match kustomization.yaml) 12 | org.opennms.instance.id: K8S 13 | # SNMP4J Options 14 | snmp4j.LogFactory: org.snmp4j.log.Log4jLogFactory 15 | org.snmp4j.smisyntaxes: opennms-snmp4j-smisyntaxes.properties 16 | org.opennms.snmp.snmp4j.allowSNMPv2InV1: 'false' 17 | org.opennms.snmp.snmp4j.forwardRuntimeExceptions: 'false' 18 | org.opennms.snmp.snmp4j.noGetBulk: 'false' 19 | org.opennms.snmp.workarounds.allow64BitIpAddress: 'true' 20 | org.opennms.snmp.workarounds.allowZeroLengthIpAddress: 'true' 21 | 22 | # Inter Process Communication 23 | ipc: 24 | grpc: 25 | host: "grpc.aws.agalue.net" 26 | port: "443" 27 | tls.enabled: "true" 28 | client.cert.filepath: /opt/minion/etc/client.pem 29 | client.private.key.filepath: /opt/minion/etc/client-key.pem 30 | sink: 31 | offheap: 32 | offHeapSize: "128MB" 33 | entriesAllowedOnHeap: 100000 34 | offHeapFilePath: "" 35 | 36 | # All the following is optional and exists for demo purposes. 37 | 38 | # Flow Processing 39 | telemetry: 40 | flows: 41 | listeners: 42 | Flow-Listener: 43 | class-name: "org.opennms.netmgt.telemetry.listeners.UdpListener" 44 | parameters: 45 | port: 8877 46 | parsers: 47 | Netflow-9: 48 | class-name: "org.opennms.netmgt.telemetry.protocols.netflow.parser.Netflow9UdpParser" 49 | queue: 50 | use-routing-key: "true" 51 | parameters: 52 | dnsLookupsEnabled: "false" # Because we cannot specify DNS settings via org.opennms.features.dnsresolver.netty.cfg 53 | Netflow-5: 54 | class-name: "org.opennms.netmgt.telemetry.protocols.netflow.parser.Netflow5UdpParser" 55 | queue: 56 | use-routing-key: "true" 57 | parameters: 58 | dnsLookupsEnabled: "false" # Because we cannot specify DNS settings via org.opennms.features.dnsresolver.netty.cfg 59 | IPFIX: 60 | class-name: "org.opennms.netmgt.telemetry.protocols.netflow.parser.IpfixUdpParser" 61 | queue: 62 | use-routing-key: "true" 63 | parameters: 64 | dnsLookupsEnabled: "false" # Because we cannot specify DNS settings via org.opennms.features.dnsresolver.netty.cfg 65 | SFlow: 66 | class-name: "org.opennms.netmgt.telemetry.protocols.sflow.parser.SFlowUdpParser" 67 | queue: 68 | use-routing-key: "true" 69 | parameters: 70 | dnsLookupsEnabled: "false" # Because we cannot specify DNS settings via org.opennms.features.dnsresolver.netty.cfg 71 | BMP-Listener: 72 | class-name: "org.opennms.netmgt.telemetry.listeners.TcpListener" 73 | parameters: 74 | port: 11019 75 | parsers: 76 | BMP: 77 | class-name: org.opennms.netmgt.telemetry.protocols.bmp.parser.BmpParser 78 | 79 | # Trap/Syslog Reception 80 | netmgt: 81 | syslog: 82 | syslog.listen.interface: "0.0.0.0" 83 | syslog.listen.port: 1514 84 | # To control how many traps are included in a single message sent to the broker 85 | syslog.batch.size: 1000 86 | # To limit how many messages are kept in memory if the broker is unreachable 87 | syslog.queue.size: 10000 88 | traps: 89 | trapd.listen.interface: "0.0.0.0" 90 | trapd.listen.port: 1162 91 | # To control how many traps are included in a single message sent to the broker 92 | trapd.batch.size: 1000 93 | # To limit how many messages are kept in memory if the broker is unreachable 94 | trapd.queue.size: 10000 95 | -------------------------------------------------------------------------------- /reduced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | bases: 5 | - ../manifests 6 | 7 | patchesStrategicMerge: 8 | - patches/cassandra.reaper.yaml 9 | - patches/cassandra.yaml 10 | - patches/elastic-hq.yaml 11 | - patches/elasticsearch.data.yaml 12 | - patches/elasticsearch.master.yaml 13 | - patches/grafana.yaml 14 | - patches/grpc-server.yaml 15 | - patches/hasura.yaml 16 | - patches/kafka.yaml 17 | - patches/kibana.yaml 18 | - patches/opennms.alec.yaml 19 | - patches/opennms.core.yaml 20 | - patches/opennms.minion.yaml 21 | - patches/opennms.nephron.yaml 22 | - patches/opennms.sentinel.yaml 23 | - patches/opennms.ui.yaml 24 | - patches/postgresql.yaml 25 | - patches/zookeeper.yaml 26 | -------------------------------------------------------------------------------- /reduced/patches/cassandra.reaper.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: cassandra-reaper 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: cassandra-reaper 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /reduced/patches/cassandra.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: cassandra 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | containers: 14 | - name: cassandra 15 | resources: 16 | limits: 17 | memory: 3Gi 18 | cpu: 1000m 19 | requests: 20 | memory: 2Gi 21 | cpu: 250m 22 | -------------------------------------------------------------------------------- /reduced/patches/elastic-hq.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: elastichq 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: elastichq 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /reduced/patches/elasticsearch.data.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: esdata 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | initContainers: 14 | - name: dependencies 15 | $patch: delete 16 | containers: 17 | - name: esdata 18 | env: 19 | - name: xpack.security.enabled 20 | $patch: delete 21 | - name: cluster.initial_master_nodes 22 | $patch: delete 23 | - name: discovery.seed_hosts 24 | $patch: delete 25 | - name: node.master 26 | value: 'true' 27 | - name: discovery.type 28 | value: single-node 29 | - name: bootstrap.memory_lock 30 | value: 'false' 31 | resources: 32 | limits: 33 | memory: 2Gi 34 | cpu: 500m 35 | requests: 36 | memory: 1Gi 37 | cpu: 250m 38 | -------------------------------------------------------------------------------- /reduced/patches/elasticsearch.master.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: esmaster 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: StatefulSet 15 | metadata: 16 | name: esmaster 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /reduced/patches/grafana.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: grafana 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | -------------------------------------------------------------------------------- /reduced/patches/grpc-server.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: grpc-server 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | containers: 14 | - name: grpc-server 15 | resources: 16 | limits: 17 | memory: 64Mi 18 | cpu: 50m 19 | requests: 20 | memory: 32Mi 21 | cpu: 20m 22 | -------------------------------------------------------------------------------- /reduced/patches/hasura.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: hasura 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: hasura 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /reduced/patches/kafka.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: kafka 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | containers: 14 | - name: kafka 15 | env: 16 | - name: KAFKA_DEFAULT_REPLICATION_FACTOR 17 | value: '1' 18 | - name: KAFKA_MIN_INSYNC_REPLICAS 19 | value: '1' 20 | resources: 21 | limits: 22 | memory: 1Gi 23 | cpu: 200m 24 | requests: 25 | memory: 512Mi 26 | cpu: 100m 27 | -------------------------------------------------------------------------------- /reduced/patches/kibana.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: kibana 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: kibana 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /reduced/patches/opennms.alec.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: apps/v1 6 | kind: StatefulSet 7 | metadata: 8 | name: alec 9 | namespace: opennms 10 | -------------------------------------------------------------------------------- /reduced/patches/opennms.core.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: onms 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: onms 14 | resources: 15 | limits: 16 | memory: 3Gi 17 | cpu: 1000m 18 | requests: 19 | memory: 2Gi 20 | cpu: 500m 21 | -------------------------------------------------------------------------------- /reduced/patches/opennms.minion.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: minion 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | containers: 14 | - name: minion 15 | resources: 16 | limits: 17 | memory: 1Gi 18 | cpu: 250m 19 | requests: 20 | memory: 512Mi 21 | cpu: 100m 22 | -------------------------------------------------------------------------------- /reduced/patches/opennms.nephron.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: flink-jobmanager 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: StatefulSet 15 | metadata: 16 | name: flink-jobmanager 17 | namespace: opennms 18 | 19 | --- 20 | $patch: delete 21 | apiVersion: apps/v1 22 | kind: StatefulSet 23 | metadata: 24 | name: flink-tm 25 | namespace: opennms 26 | 27 | --- 28 | $patch: delete 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | metadata: 32 | name: nephron 33 | namespace: opennms 34 | -------------------------------------------------------------------------------- /reduced/patches/opennms.sentinel.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: autoscaling/v1 6 | kind: HorizontalPodAutoscaler 7 | metadata: 8 | name: sentinel 9 | namespace: opennms 10 | 11 | --- 12 | apiVersion: apps/v1 13 | kind: StatefulSet 14 | metadata: 15 | name: sentinel 16 | namespace: opennms 17 | spec: 18 | replicas: 1 19 | template: 20 | spec: 21 | affinity: 22 | $patch: delete 23 | containers: 24 | - name: sentinel 25 | resources: 26 | limits: 27 | memory: 1Gi 28 | cpu: 250m 29 | requests: 30 | memory: 512Mi 31 | cpu: 100m 32 | -------------------------------------------------------------------------------- /reduced/patches/opennms.ui.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | $patch: delete 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: opennms-ui 9 | namespace: opennms 10 | 11 | --- 12 | $patch: delete 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: onms-ui 17 | namespace: opennms 18 | -------------------------------------------------------------------------------- /reduced/patches/postgresql.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: postgres 8 | namespace: opennms 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - name: postgres 14 | args: 15 | - postgres 16 | volumeMounts: 17 | - mountPath: /etc/postgresql.conf 18 | $patch: delete 19 | resources: 20 | limits: 21 | memory: 256Mi 22 | cpu: 200m 23 | requests: 24 | memory: 128Mi 25 | cpu: 100m 26 | volumes: 27 | - name: postgresql-config 28 | $patch: delete 29 | -------------------------------------------------------------------------------- /reduced/patches/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: StatefulSet 6 | metadata: 7 | name: zk 8 | namespace: opennms 9 | spec: 10 | replicas: 1 11 | template: 12 | spec: 13 | containers: 14 | - name: zk 15 | env: 16 | - name: ZOO_SERVERS 17 | value: server.1=zk-0.zookeeper.opennms.svc.cluster.local:2888:3888;2181 18 | - name: ZOO_STANDALONE_ENABLED 19 | value: 'true' 20 | resources: 21 | limits: 22 | memory: 256Mi 23 | cpu: 200m 24 | requests: 25 | memory: 128Mi 26 | cpu: 100m 27 | -------------------------------------------------------------------------------- /serverless/.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | -------------------------------------------------------------------------------- /serverless/README.fission.md: -------------------------------------------------------------------------------- 1 | # Fission 2 | 3 | ## Install Fission 4 | 5 | Make sure to install the fission CLI on your own computer, as explained on the [documentation](https://docs.fission.io/installation/). 6 | 7 | For the manifets, it is enough to have the core functionality, with the Kafka Listener implemented. As Helm is not used here, this can easily be done by executing the following commands: 8 | 9 | ```bash 10 | export RELEASE=$(curl -s https://api.github.com/repos/fission/fission/releases/latest | grep tag_name | cut -d '"' -f 4) 11 | kubectl create namespace fission 12 | kubectl -n fission apply -f https://github.com/fission/fission/releases/download/$RELEASE/fission-core-$RELEASE.yaml 13 | kubectl -n fission apply -f fission-mqtrigger-kafka.yaml 14 | ``` 15 | 16 | > For Minikube use `https://github.com/fission/fission/releases/download/$RELEASE/fission-core-$RELEASE-minikube.yaml` 17 | 18 | The last command applies the YAML that contains the Kafka `mqtrigger` which is not included/enabled by default with Fission. 19 | 20 | It has been done this way because it doesn't look possible to use `fission-core` with `mqtrigger-kafka` through Helm, as the Kafka feature is part of `fission-all`, which contains features not required here. 21 | 22 | ## Create the secret with configuration 23 | 24 | Once you have the WebHook URL, add it to a `secret`, as well as the OpenNMS WebUI URL; for example: 25 | 26 | ```bash 27 | SLACK_URL="https://hooks.slack.com/services/xxx/yyy/zzzz" 28 | ONMS_URL="https://onmsui.aws.agalue.net/opennms" 29 | 30 | kubectl create secret generic serverless-config \ 31 | --namespace opennms \ 32 | --from-literal=SLACK_URL="$SLACK_URL" \ 33 | --from-literal=ONMS_URL="$ONMS_URL" \ 34 | --dry-run=client -o yaml | kubectl apply -f - 35 | ``` 36 | 37 | > **WARNING**: do not forget to fix the Slack URL. 38 | 39 | ## Create the NodeJS Environment 40 | 41 | ```bash 42 | fission environment create \ 43 | --envNamespace opennms \ 44 | --name nodejs \ 45 | --image fission/node-env \ 46 | --builder fission/node-builder \ 47 | --poolsize 1 48 | ``` 49 | 50 | ## Create a ZIP with the NodeJS app and its dependencies 51 | 52 | The `slack-forwarder` directory contains the NodeJS application and the dependencies file. 53 | 54 | ```bash 55 | zip -j alarm2slack.zip slack-forwarder/package.json slack-forwarder/alarm2slack.js 56 | ``` 57 | 58 | > IMPORTANT: all the relevant files should be at the root of the ZIP (hence, the `-j`). 59 | 60 | ## Create the function 61 | 62 | ```bash 63 | fission function create \ 64 | --name alarm2slack \ 65 | --envNamespace opennms \ 66 | --fnNamespace opennms \ 67 | --src alarm2slack.zip \ 68 | --env nodejs \ 69 | --secret serverless-config \ 70 | --entrypoint "alarm2slack.fission" 71 | ``` 72 | 73 | ## Create the function trigger based on a Kafka Topic 74 | 75 | ```bash 76 | fission mqt create \ 77 | --name alarm2slack \ 78 | --fnNamespace opennms \ 79 | --function alarm2slack \ 80 | --mqtype kafka \ 81 | --topic K8S_enhanced_alarms 82 | ``` 83 | 84 | > **IMPORTANT**: The name of the topic relies on the Kafka Producer Enhancer YAML file. 85 | 86 | ## Testing 87 | 88 | The best way to test is by generating an actual alarm in OpenNMS. This method works. 89 | 90 | The following alternative options are valid, but they are not working, probably due to how `fission` has been installed: 91 | 92 | [1] Using the test command: 93 | 94 | ```bash 95 | fission function test --name alarm2slack --fnNamespace opennms --body '{ 96 | "alarm": { 97 | "id": 666, 98 | "uei": "uei.jigsaw/test", 99 | "severity": 6, 100 | "last_event_time": 1560438592000, 101 | "last_event": { "id": 66, "parameter": [{"name":"owner","value":"agalue"}] }, 102 | "log_message": "I want to play a game", 103 | "description": "

Hope to hear from your soon!

" 104 | }, 105 | "node": { 106 | "id": 6, 107 | "label": "lucifer01", 108 | "foreign_source": "hell", 109 | "foreign_id": "666" 110 | } 111 | }' 112 | ``` 113 | 114 | [2] Using an HTTP trigger: 115 | 116 | ```bash 117 | export DOMAIN="aws.agalue.net" 118 | 119 | fission route create \ 120 | --name alarm2slack \ 121 | --function alarm2slack \ 122 | --method POST \ 123 | --url /alarm2slack \ 124 | --host fission.$DOMAIN \ 125 | --createingress 126 | ``` 127 | 128 | Then, 129 | 130 | ```bash 131 | curl -H 'Content-Type: application/json' -v -d '{ 132 | "alarm": { 133 | "id": 666, 134 | "uei": "uei.jigsaw/test", 135 | "severity": 6, 136 | "last_event_time": 1560438592000, 137 | "last_event": { "id": 66, "parameter": [{"name":"owner","value":"agalue"}] }, 138 | "log_message": "I want to play a game", 139 | "description": "

Hope to hear from your soon!

" 140 | }, 141 | "node": { 142 | "id": 6, 143 | "label": "lucifer01", 144 | "foreign_source": "hell", 145 | "foreign_id": "666" 146 | } 147 | }' http://fission.$DOMAIN/alarm2slack 148 | ``` 149 | -------------------------------------------------------------------------------- /serverless/README.knative.md: -------------------------------------------------------------------------------- 1 | # Knative 2 | 3 | > **This is a work in progress, usability not guaranteed** 4 | 5 | In this tutorial, a very simple and simplified installation of [Istio](https://istio.io) and [Knative](https://knative.dev/) will be performed. All tracing/logging/observability features won/t be used to simplify the deployment. 6 | 7 | The following outlines the installation steps, but all of them have been placed on the script [setup-istio-knative.sh](./setup-istio-knative.sh) 8 | 9 | > **IMPORTANT**: This requires Kubernetes 1.18 or newer. 10 | 11 | Declare a variable with the desired Knative version you would like to use: 12 | 13 | ```bash 14 | export knative_version="v1.0.0" 15 | ``` 16 | 17 | The above will be used on all subsequent commands. 18 | 19 | ## Install Knative Serving 20 | 21 | ```bash 22 | kubectl apply -f "https://github.com/knative/serving/releases/download/knative-${knative_version}/serving-crds.yaml" 23 | kubectl apply -f "https://github.com/knative/serving/releases/download/knative-${knative_version}/serving-core.yaml" 24 | ``` 25 | 26 | ## Install a Networking Layer (Istio) 27 | 28 | ```bash 29 | kubectl apply -l knative.dev/crd-install=true -f "https://github.com/knative/net-istio/releases/download/knative-${knative_version}/istio.yaml" 30 | kubectl apply -f "https://github.com/knative/net-istio/releases/download/knative-${knative_version}/istio.yaml" 31 | kubectl apply -f "https://github.com/knative/net-istio/releases/download/knative-${knative_version}/net-istio.yaml" 32 | ``` 33 | 34 | Label default namespace for auto-injection. 35 | 36 | ```bash 37 | kubectl label namespace default istio-injection=enabled --overwrite=true 38 | ``` 39 | 40 | The above is for convenience, to facilitate the injection of the sidecars for the knative related pods. 41 | 42 | ## Fix the Domain Configuration 43 | 44 | ```bash 45 | DOMAIN="aws.agalue.net" 46 | 47 | cat < **WARNING**: do not forget to use your own domain. 59 | 60 | ## Install Knative Eventing 61 | 62 | ```bash 63 | kubectl apply -f "https://github.com/knative/eventing/releases/download/knative-${knative_version}/eventing-crds.yaml" 64 | kubectl apply -f "https://github.com/knative/eventing/releases/download/knative-${knative_version}/eventing-core.yaml" 65 | kubectl apply -f "https://github.com/knative-sandbox/eventing-kafka-broker/releases/download/knative-${knative_version}/eventing-kafka-controller.yaml" 66 | kubectl apply -f "https://github.com/knative-sandbox/eventing-kafka-broker/releases/download/knative-${knative_version}/eventing-kafka-broker.yaml" 67 | ``` 68 | 69 | ## Create the secret with configuration 70 | 71 | Once you have the Slack WebHook URL, add it to a `secret`, as well as the OpenNMS WebUI URL; for example: 72 | 73 | ```bash 74 | SLACK_URL="https://hooks.slack.com/services/xxx/yyy/zzzz" 75 | ONMS_URL="https://onmsui.aws.agalue.net/opennms" 76 | 77 | kubectl create secret generic serverless-config \ 78 | --namespace default \ 79 | --from-literal=SLACK_URL="$SLACK_URL" \ 80 | --from-literal=ONMS_URL="$ONMS_URL" \ 81 | --dry-run=client -o yaml | kubectl apply -f - 82 | ``` 83 | 84 | > **WARNING**: do not forget to fix the Slack URL. 85 | 86 | ## Install the Knative Service 87 | 88 | This service represents the `function` or the code that will be executed every time a message has been sent to a specific in kafka. 89 | 90 | ```bash 91 | kubectl apply -f knative-service.yaml 92 | ``` 93 | 94 | > **WARNING**: make sure that the image from [slack-forwarder-go](./slack-forwarder-go) has been created and uploaded to Docker Hub. If a different account is used, make sure to adjust the YAML file. 95 | 96 | ## Install and Kafka Source controller 97 | 98 | This will trigger the desired Knative service when a message is received from a given Kafka topic. 99 | 100 | ```bash 101 | kubectl apply -f knative-kafka-source.yaml 102 | ``` 103 | 104 | Note that we specify the kafka Consumer Grup, the Kafka Cluster Bootstrap Server, the Kafka Topic and the `ksvc` that will be triggered when a new messages is received from the topic. 105 | 106 | > **IMPORTANT**: Make sure to use the topic maintained by `agalue/kafka-converter-go`, as it is expected to receive a JSON payload. 107 | -------------------------------------------------------------------------------- /serverless/README.kubeless.md: -------------------------------------------------------------------------------- 1 | # Kubeless 2 | 3 | ## Install Kubeless 4 | 5 | Make sure to install the kubeless CLI on your own computer, as explained on the [documentation](https://kubeless.io/docs/quick-start/). 6 | 7 | For the manifets, with the Kafka Listener, this can easily be done by executing the following commands: 8 | 9 | ```bash 10 | export RELEASE=$(curl -s https://api.github.com/repos/kubeless/kubeless/releases/latest | grep tag_name | cut -d '"' -f 4) 11 | kubectl create ns kubeless 12 | kubectl apply -f https://github.com/kubeless/kubeless/releases/download/$RELEASE/kubeless-$RELEASE.yaml 13 | kubectl apply -f kubeless-mqtrigger-kafka.yaml 14 | ``` 15 | 16 | ## Create the secret with configuration 17 | 18 | Once you have the WebHook URL, add it to a `secret`, as well as the OpenNMS WebUI URL; for example: 19 | 20 | ```bash 21 | SLACK_URL="https://hooks.slack.com/services/xxx/yyy/zzzz" 22 | ONMS_URL="https://onmsui.aws.agalue.net/opennms" 23 | 24 | kubectl -n opennms create secret generic serverless-config \ 25 | --from-literal=SLACK_URL="$SLACK_URL" \ 26 | --from-literal=ONMS_URL="$ONMS_URL" \ 27 | --dry-run=client -o yaml | kubectl apply -f - 28 | ``` 29 | 30 | > **WARNING**: do not forget to fix the Slack URL. 31 | 32 | ## Create the function 33 | 34 | ```bash 35 | kubeless function deploy alarm2slack \ 36 | --namespace opennms \ 37 | --runtime nodejs10 \ 38 | --dependencies ./slack-forwarder/package.json \ 39 | --from-file ./slack-forwarder/alarm2slack.js \ 40 | --handler alarm2slack.kubeless \ 41 | --secrets serverless-config 42 | ``` 43 | 44 | ## Create the function trigger based on a Kafka Topic 45 | 46 | ```bash 47 | kubeless trigger kafka create alarm2slack \ 48 | --namespace opennms \ 49 | --function-selector created-by=kubeless,function=alarm2slack \ 50 | --trigger-topic K8S_enhanced_alarms 51 | ``` 52 | 53 | > **IMPORTANT**: The name of the topic relies on the Kafka Producer Enhancer YAML file. 54 | 55 | Use `kubeless function list` to check whether the function is ready to use. 56 | 57 | ## Testing 58 | 59 | ```bash 60 | kubeless function call alarm2slack \ 61 | -n opennms \ 62 | --data '{ 63 | "alarm": { 64 | "id": 666, 65 | "uei": "uei.jigsaw/test", 66 | "severity": 6, 67 | "last_event_time": 1560438592000, 68 | "last_event": { "id": 66, "parameter": [{"name":"owner","value":"agalue"}] }, 69 | "log_message": "I want to play a game", 70 | "description": "

Hope to hear from your soon!

" 71 | }, 72 | "node": { 73 | "id": 6, 74 | "label": "lucifer01", 75 | "foreign_source": "hell", 76 | "foreign_id": "666" 77 | } 78 | }' 79 | ``` 80 | -------------------------------------------------------------------------------- /serverless/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Introduction to Serverless 3 | 4 | There are hundreds of possible ways to use Serverless technologies with OpenNMS and Kubernetes. In this particular case, the idea is to have a simple function to react when an alarm from OpenNMS is sent to a Kafka topic, and forward it to a given Slack Channel as a message. 5 | 6 | For this, it is required to have the Kafka producer feature enabled on the OpenNMS. 7 | 8 | In essence, and in terms of OpenNMS, this can be seen as a scalable custom OpenNMS NBI implementation, or a scalable Notification system based on alarms, or a scalable Scriptd alternative based on alarms. 9 | 10 | Of course, the events are also available through Kafka topics, but alarms are often more important in terms of notifying a user when a problem is discovered. 11 | 12 | ## Installation 13 | 14 | ### Deploy the Kafka Converter Application 15 | 16 | The Kafka Producer feature of OpenNMS publishes events, alarms, metrics and nodes to topics using Google Protobuf as the payload format. The `.proto` files are defined [here](https://github.com/OpenNMS/opennms/tree/develop/features/kafka/producer/src/main/proto). 17 | 18 | Unfortunately, serverless controllers like Fission, Kubeless and Knative expect plain text or to be more presice, a JSON as the payload. For this reason, it is necessary to convert the GPB messages to JSON messages. 19 | 20 | [Here](https://github.com/agalue/producer-enhancer) you'll find an application implemented in Go to enhance an Alarm with Node data when available and forward the result in JSON to another topic. 21 | 22 | ### Create a Slack WebHook 23 | 24 | Follow the Slack API [documentation](https://api.slack.com/incoming-webhooks) to create a Webhook to send messages to a given channel. 25 | 26 | ### Deploy the Function 27 | 28 | * Using [Fission](README.fission.md) 29 | * Using [Kubeless](README.kubeless.md) 30 | * Using [Knative](README.knative.md) 31 | 32 | ## Test 33 | 34 | From now on, when an alarm is generated in OpenNMS, the Kafka Producer will forward it to Kafka. From there, the converter will put the JSON version of the GPB alarm to another topic. From there, the Serverless Message Queue Listener will grab it and call the function. Finally, the function will transform the alarm into a message, and will post it on Slack. 35 | -------------------------------------------------------------------------------- /serverless/fission-mqtrigger-kafka.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: mqtrigger-kafka 8 | labels: 9 | svc: mqtrigger 10 | messagequeue: kafka 11 | spec: 12 | replicas: 1 13 | selector: 14 | matchLabels: 15 | svc: mqtrigger 16 | messagequeue: kafka 17 | template: 18 | metadata: 19 | labels: 20 | svc: mqtrigger 21 | messagequeue: kafka 22 | spec: 23 | serviceAccountName: fission-svc 24 | containers: 25 | - name: mqtrigger 26 | image: fission/fission-bundle 27 | imagePullPolicy: IfNotPresent 28 | command: ["/fission-bundle"] 29 | args: ["--mqt", "--routerUrl", "http://router.fission.svc.cluster.local"] 30 | env: 31 | - name: MESSAGE_QUEUE_TYPE 32 | value: kafka 33 | - name: MESSAGE_QUEUE_URL 34 | value: "kafka.opennms.svc.cluster.local:9092" 35 | - name: MESSAGE_QUEUE_KAFKA_VERSION 36 | value: "2.7.0" 37 | resources: 38 | limits: 39 | cpu: 100m 40 | memory: 256Mi 41 | 42 | -------------------------------------------------------------------------------- /serverless/knative-kafka-source.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: sources.knative.dev/v1beta1 5 | kind: KafkaSource 6 | metadata: 7 | name: kafka-source 8 | namespace: default 9 | spec: 10 | consumerGroup: knative-group 11 | bootstrapServers: 12 | - kafka.opennms.svc.cluster.local:9092 13 | topics: 14 | - K8S_enhanced_alarms 15 | sink: 16 | ref: 17 | apiVersion: serving.knative.dev/v1 18 | kind: Service 19 | name: slack-forwarder 20 | -------------------------------------------------------------------------------- /serverless/knative-service.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: serving.knative.dev/v1 5 | kind: Service 6 | metadata: 7 | name: slack-forwarder 8 | namespace: default 9 | spec: 10 | template: 11 | spec: 12 | containers: 13 | - image: agalue/slack-forwarder 14 | imagePullPolicy: Always 15 | env: 16 | - name: SLACK_URL 17 | valueFrom: 18 | secretKeyRef: 19 | key: SLACK_URL 20 | name: serverless-config 21 | - name: ONMS_URL 22 | valueFrom: 23 | secretKeyRef: 24 | key: ONMS_URL 25 | name: serverless-config -------------------------------------------------------------------------------- /serverless/kubeless-mqtrigger-kafka.yaml: -------------------------------------------------------------------------------- 1 | # @author Alejandro Galue 2 | 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | labels: 8 | kubeless: kafka-trigger-controller 9 | name: kafka-trigger-controller 10 | namespace: kubeless 11 | spec: 12 | selector: 13 | matchLabels: 14 | kubeless: kafka-trigger-controller 15 | template: 16 | metadata: 17 | labels: 18 | kubeless: kafka-trigger-controller 19 | spec: 20 | serviceAccountName: controller-acct 21 | containers: 22 | - name: kafka-trigger-controller 23 | image: kubeless/kafka-trigger-controller 24 | imagePullPolicy: IfNotPresent 25 | env: 26 | - name: KAFKA_BROKERS 27 | value: kafka.opennms.svc.cluster.local:9092 28 | resources: 29 | limits: 30 | cpu: 100m 31 | memory: 256Mi 32 | 33 | --- 34 | apiVersion: apiextensions.k8s.io/v1beta1 35 | kind: CustomResourceDefinition 36 | metadata: 37 | name: kafkatriggers.kubeless.io 38 | spec: 39 | group: kubeless.io 40 | names: 41 | kind: KafkaTrigger 42 | plural: kafkatriggers 43 | singular: kafkatrigger 44 | scope: Namespaced 45 | version: v1beta1 46 | 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: kafka-controller-deployer 52 | roleRef: 53 | apiGroup: rbac.authorization.k8s.io 54 | kind: ClusterRole 55 | name: kafka-controller-deployer 56 | subjects: 57 | - kind: ServiceAccount 58 | name: controller-acct 59 | namespace: kubeless 60 | 61 | --- 62 | apiVersion: rbac.authorization.k8s.io/v1 63 | kind: ClusterRole 64 | metadata: 65 | name: kafka-controller-deployer 66 | rules: 67 | - apiGroups: 68 | - "" 69 | resources: 70 | - services 71 | - configmaps 72 | verbs: 73 | - get 74 | - list 75 | - apiGroups: 76 | - kubeless.io 77 | resources: 78 | - functions 79 | - kafkatriggers 80 | verbs: 81 | - get 82 | - list 83 | - watch 84 | - update 85 | - delete 86 | -------------------------------------------------------------------------------- /serverless/setup-istio-knative.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | NO_COLOR=${NO_COLOR:-""} 6 | if [ -z "$NO_COLOR" ]; then 7 | header=$'\e[1;33m' 8 | reset=$'\e[0m' 9 | else 10 | header='' 11 | reset='' 12 | fi 13 | 14 | if [ "${slack_url}" == "" ]; then 15 | echo "ERROR: the slack_url environment variable is required" 16 | exit 1 17 | fi 18 | 19 | function header_text { 20 | echo "$header$*$reset" 21 | } 22 | 23 | knative_version="v1.0.0" 24 | 25 | domain="${domain-aws.agalue.net}" 26 | kafka_server="kafka.opennms.svc.cluster.local:9092" 27 | onms_url="${onms_url-https://onmsui.$domain/opennms}" 28 | 29 | header_text "Starting Knative..." 30 | 31 | header_text "Using Knative Version: ${knative_version}" 32 | header_text "Using Kafka Server: ${kafka_server}" 33 | header_text "Using OpenNMS UI Server ${onms_url}" 34 | 35 | header_text "Labeling default namespace w/ istio-injection=enabled" 36 | kubectl label namespace default istio-injection=enabled --overwrite=true 37 | 38 | header_text "Setting up Knative Serving" 39 | kubectl apply -f "https://github.com/knative/serving/releases/download/knative-${knative_version}/serving-crds.yaml" 40 | kubectl apply -f "https://github.com/knative/serving/releases/download/knative-${knative_version}/serving-core.yaml" 41 | 42 | header_text "Setting up Network Layer - Istio" 43 | kubectl apply -l knative.dev/crd-install=true -f "https://github.com/knative/net-istio/releases/download/knative-${knative_version}/istio.yaml" 44 | kubectl apply -f "https://github.com/knative/net-istio/releases/download/knative-${knative_version}/istio.yaml" 45 | kubectl apply -f "https://github.com/knative/net-istio/releases/download/knative-${knative_version}/net-istio.yaml" 46 | 47 | header_text "Waiting for istio to become ready" 48 | sleep 10; while echo && kubectl get pods -n istio-system | grep -v -E "(Running|Completed|STATUS)"; do sleep 10; done 49 | 50 | header_text "Configuring custom domain" 51 | cat < *NOTE*: Please use your own Docker Hub account or use the image provided on my account. 13 | -------------------------------------------------------------------------------- /serverless/slack-forwarder-go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/agalue/slack-forwarder 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/cloudevents/sdk-go/v2 v2.0.0-preview8 7 | github.com/lunny/html2md v0.0.0-20181018071239-7d234de44546 8 | gotest.tools v2.2.0+incompatible 9 | ) 10 | -------------------------------------------------------------------------------- /serverless/slack-forwarder-go/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | 12 | cloudevents "github.com/cloudevents/sdk-go/v2" 13 | "gotest.tools/assert" 14 | ) 15 | 16 | func TestReceive(t *testing.T) { 17 | testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 18 | assert.Equal(t, http.MethodPost, req.Method) 19 | bytes, err := ioutil.ReadAll(req.Body) 20 | assert.NilError(t, err) 21 | msg := SlackMessage{} 22 | err = json.Unmarshal(bytes, &msg) 23 | assert.NilError(t, err) 24 | assert.Assert(t, len(msg.Attachments) == 1) 25 | bytes, err = json.MarshalIndent(msg, "", " ") 26 | assert.NilError(t, err) 27 | fmt.Println(string(bytes)) 28 | att := msg.Attachments[0] 29 | assert.Equal(t, "Alarm ID: 666", att.Title) 30 | assert.Equal(t, "Something **bad** happened", att.PreText) 31 | assert.Equal(t, 3, len(att.Fields)) 32 | assert.Equal(t, "Major", att.Fields[0].Value) 33 | assert.Equal(t, "srv01; ID Test:001(1)", att.Fields[1].Value) 34 | assert.Equal(t, "agalue", att.Fields[2].Value) 35 | res.WriteHeader(http.StatusOK) 36 | })) 37 | defer testServer.Close() 38 | 39 | slackURL = testServer.URL 40 | 41 | data := EnhancedAlarm{ 42 | Alarm: &Alarm{ 43 | ID: 666, 44 | UEI: "uei.opennms.org/test", 45 | LogMessage: "

Something bad happened

", 46 | Description: "

Check your stuff

", 47 | Severity: 6, 48 | NodeCriteria: &NodeCriteria{ 49 | ID: 1, 50 | ForeignSource: "Test", 51 | ForeignID: "001", 52 | }, 53 | LastEventTime: 1000000, 54 | LastEvent: &Event{ 55 | ID: 66, 56 | Parameters: []EventParameter{ 57 | { 58 | Name: "Owner", 59 | Value: "agalue", 60 | }, 61 | }, 62 | }, 63 | }, 64 | Node: &Node{ 65 | ID: 1, 66 | ForeignSource: "Test", 67 | ForeignID: "001", 68 | Label: "srv01", 69 | }, 70 | } 71 | 72 | event := cloudevents.NewEvent() 73 | event.SetData("application/json", data) 74 | receive(context.Background(), event) 75 | } 76 | -------------------------------------------------------------------------------- /serverless/slack-forwarder/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /serverless/slack-forwarder/__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | // @author Alejandro Galue 2 | 3 | 'use strict'; 4 | 5 | module.exports = { 6 | post: jest.fn(() => Promise.resolve({ 7 | status: 200, 8 | statusText: 'OK', 9 | data: null 10 | })) 11 | } -------------------------------------------------------------------------------- /serverless/slack-forwarder/alarm2slack.js: -------------------------------------------------------------------------------- 1 | // @author Alejandro Galue 2 | // This is intended to work with either Fission or Kubeless 3 | // SLACK_URL and ONMS_URL should be provided as a secret or environment variable. 4 | 5 | 'use strict'; 6 | 7 | const axios = require('axios'); 8 | const mrkdwn = require('html-to-mrkdwn'); 9 | const fs = require('fs'); 10 | 11 | const severityColors = [ 12 | '#000', 13 | '#999000', 14 | '#999', 15 | '#336600', 16 | '#ffcc00', 17 | '#ff9900', 18 | '#ff3300', 19 | '#cc0000' 20 | ]; 21 | 22 | const severityNames = [ 23 | 'Unknown', 24 | 'Indeterminate', 25 | 'Cleared', 26 | 'Normal', 27 | 'Warning', 28 | 'Minor', 29 | 'Major', 30 | 'Critical' 31 | ]; 32 | 33 | const mandatoryFields = [ 34 | 'id', 35 | 'log_message', 36 | 'description', 37 | 'severity', 38 | 'last_event', 39 | 'last_event_time' 40 | ]; 41 | 42 | function getConfig(attributeName) { 43 | let paths = [ 44 | '/secrets/opennms/serverless-config', 45 | '/secrets/default/serverless-config', 46 | '/serverless-config' 47 | ]; 48 | for (var i=0; i < paths.length; i++) { 49 | var configPath = `${paths[i]}/${attributeName}`; 50 | console.log(`Validating path ${configPath}`); 51 | if (fs.existsSync(configPath)) { 52 | let value = fs.readFileSync(configPath,'utf8'); 53 | console.log(`${attributeName} is ${value} from ${configPath}`); 54 | return value; 55 | } 56 | } 57 | return process.env[attributeName]; 58 | } 59 | 60 | function buildMessage(alarm, node) { 61 | let attachment = { 62 | title: `Alarm ID: ${alarm.id}`, 63 | title_link: `${globalOnmsUrl}/alarm/detail.htm?id=${alarm.id}`, 64 | color: severityColors[alarm.severity], 65 | pretext: mrkdwn(alarm.log_message).text, 66 | text: mrkdwn(alarm.description).text, 67 | ts: alarm.last_event_time/1000 | 0, 68 | fields: [{ 69 | title: 'Severity', 70 | value: severityNames[alarm.severity], 71 | short: true 72 | }] 73 | } 74 | if (node) { 75 | const id = node.foreign_id ? `${node.foreign_source}:${node.foreign_id}(${node.id})` : `${node.id}`; 76 | attachment.fields.push({ 77 | title: 'Node', 78 | value: `${node.label}; ID ${id}`, 79 | short: false 80 | }); 81 | } 82 | if (alarm.last_event) { 83 | alarm.last_event.parameter.forEach(p => attachment.fields.push({ 84 | title: p.name, 85 | value: p.value, 86 | short: false 87 | })); 88 | } 89 | let message = { 90 | attachments: [ attachment ] 91 | }; 92 | console.log(message); 93 | return message; 94 | } 95 | 96 | async function sendAlarm(data) { 97 | console.log('Reveived: ', JSON.stringify(data, null, 2)); 98 | const { alarm, node } = data; 99 | if (!globalSlackUrl) { 100 | return { status: 400, body: 'Missing Slack Webhook URL.' }; 101 | } 102 | if (!globalOnmsUrl) { 103 | return { status: 400, body: 'Missing OpenNMS URL.' }; 104 | } 105 | for (let i=0; i 2 | 3 | 'use strict'; 4 | 5 | process.env.SLACK_URL = "https://hooks.slack.com/services/xxx/yyy/zzzz"; 6 | process.env.ONMS_URL = "https://demo.opennms.org/opennms"; 7 | 8 | const app = require('./alarm2slack'); 9 | const mrkdwn = require('html-to-mrkdwn'); 10 | const mockAxios = require('axios'); 11 | 12 | test('Test message generation', async() => { 13 | const alarm = { 14 | id: 666, 15 | uei: 'uei.test/jigsaw', 16 | log_message: 'Hello alejandro', 17 | description: '

I want to play a game.

', 18 | severity: 6, 19 | last_event_time: 1551640812345, 20 | last_event: { 21 | id: 66, 22 | parameter: [ 23 | { 24 | name: "Owner", 25 | value: "agalue" 26 | } 27 | ] 28 | }, 29 | node_criteria: { 30 | id: 6, 31 | foreign_source: "hell", 32 | foreign_id: "diablo" 33 | } 34 | }; 35 | 36 | const node = { 37 | id: 6, 38 | foreign_source: "hell", 39 | foreign_id: "diablo", 40 | label: "lucifer01", 41 | sys_object_id: ".1.3.6.1.4.1.666.1" 42 | } 43 | 44 | const message = { 45 | attachments: [{ 46 | title: `Alarm ID: ${alarm.id}`, 47 | title_link: `${process.env.ONMS_URL}/alarm/detail.htm?id=${alarm.id}`, 48 | color: '#ff3300', 49 | pretext: mrkdwn(alarm.log_message).text, 50 | text: mrkdwn(alarm.description).text, 51 | ts: 1551640812, 52 | fields: [{ 53 | title: "Severity", 54 | value: "Major", 55 | short: true 56 | },{ 57 | title: "Node", 58 | value: "lucifer01; ID hell:diablo(6)", 59 | short: false 60 | },{ 61 | title: "Owner", 62 | value: "agalue", 63 | short: false 64 | }] 65 | }] 66 | }; 67 | 68 | const data = { alarm, node } 69 | 70 | const kubelessResults = await app.kubeless({ 71 | data 72 | }); 73 | 74 | const fissionResults = await app.fission({ 75 | request: { body: data } 76 | }); 77 | 78 | expect(kubelessResults.status).toBe(200); 79 | expect(fissionResults.status).toBe(200); 80 | expect(mockAxios.post).toHaveBeenCalledTimes(2); 81 | expect(mockAxios.post).toHaveBeenCalledWith(process.env.SLACK_URL, message); 82 | }); 83 | 84 | 85 | test('Test missing fields', async() => { 86 | const test1 = await app.kubeless({data: { alarm: {} }}); 87 | const test2 = await app.kubeless({data: { alarm: {id: 666} }}); 88 | const test3 = await app.kubeless({data: { alarm: {logMessage: 'blah'} }}); 89 | 90 | expect(test1.status).toBe(400); 91 | expect(test2.status).toBe(400); 92 | expect(test3.status).toBe(400); 93 | }); 94 | -------------------------------------------------------------------------------- /serverless/slack-forwarder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alarm2slack", 3 | "version": "1.0.0", 4 | "description": "OpenNMS Alarms to Slack", 5 | "main": "alarm2slack.js", 6 | "author": "Alejandro Galue ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "axios": "^0.21.1", 10 | "html-to-mrkdwn": "^3.0.0" 11 | }, 12 | "devDependencies": { 13 | "jest": "^26.6.3" 14 | }, 15 | "scripts": { 16 | "test": "jest" 17 | }, 18 | "jest": { 19 | "testURL": "http://localhost" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /serverless/slack-forwarder/pod2slack.js: -------------------------------------------------------------------------------- 1 | // @author Alejandro Galue 2 | // 3 | // Inspiration: 4 | // https://github.com/fission/fission/blob/master/examples/nodejs/kubeEventsSlack.js 5 | // 6 | // Deployment: 7 | // zip pod2slack.zip package.json pod2slack.js 8 | // fission environment create --name nodejs --image fission/node-env:latest --builder fission/node-builder:latest --poolsize 1 9 | // fission function create --name pod2slack --src pod2slack.zip --env nodejs --entrypoint pod2slack --secret serverless-config 10 | // fission watch create --function pod2slack --type pod --ns opennms 11 | // 12 | // Future Enhancements: 13 | // 1. Elements to consider: 14 | // - metadata.creationTimestamp 15 | // - metadata.labels 16 | // - spec.nodeName 17 | // - status.hostIp 18 | // - status.podIp 19 | // 2. Headers to consider: 20 | // - x-fission-function-name 21 | // - x-fission-function-namespace 22 | // 3. Use the OpenNMS ReST API to add/remove nodes based on Pods. 23 | // - Provide ONMS_URL, ONMS_USERNAME, ONMS_PASSWORD as secrets. 24 | 25 | 'use strict'; 26 | 27 | const axios = require('axios'); 28 | const fs = require('fs'); 29 | 30 | const configPath = '/secrets/default/serverless-config/SLACK_URL'; 31 | 32 | var slackUrl = process.env.SLACK_URL; 33 | if (fs.existsSync(configPath)) { 34 | slackUrl = fs.readFileSync(configPath,'utf8'); 35 | console.log(`Slack URL: ${slackUrl}`); 36 | } 37 | 38 | function upcaseFirst(s) { 39 | return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); 40 | } 41 | 42 | module.exports = async function(context) { 43 | if (!slackUrl) { 44 | return { status: 400, body: 'Missing Slack Webhook URL.' }; 45 | } 46 | try { 47 | let eventType = context.request.get('X-Kubernetes-Event-Type'); 48 | let objType = context.request.get('X-Kubernetes-Object-Type'); 49 | let obj = context.request.body; 50 | let objName = obj.metadata.name; 51 | let objNamespace = obj.metadata.namespace; 52 | let objVersion = obj.metadata.resourceVersion; 53 | let text = `${upcaseFirst(eventType)} ${objType} ${objName}@${objNamespace} (version ${objVersion})`; 54 | console.debug(JSON.stringify(obj, null, 2)); 55 | console.log(text); 56 | const response = await axios.post(slackUrl, { text }); 57 | console.log(response.statusText); 58 | return { status: 200, body: response.statusText }; 59 | } catch (error) { 60 | console.error(error); 61 | return { status: 500, body: 'ERROR: something went wrong. ' + error }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /serverless/slack-forwarder/pod2slack.test.js: -------------------------------------------------------------------------------- 1 | // @author Alejandro Galue 2 | 3 | 'use strict'; 4 | 5 | process.env.SLACK_URL = "https://hooks.slack.com/services/xxx/yyy/zzzz"; 6 | 7 | const app = require('./pod2slack'); 8 | const mockAxios = require('axios'); 9 | 10 | test('Test message generation', async() => { 11 | const context = { 12 | request: { 13 | get: id => { 14 | switch (id) { 15 | case 'X-Kubernetes-Event-Type': return 'ADDED'; 16 | case 'X-Kubernetes-Object-Type': return 'Pod'; 17 | } 18 | }, 19 | body: { 20 | metadata: { 21 | name: 'nginx', 22 | namespace: 'default', 23 | resourceVersion: 1 24 | } 25 | } 26 | } 27 | }; 28 | 29 | const fissionResults = await app(context); 30 | expect(fissionResults.status).toBe(200); 31 | expect(mockAxios.post).toHaveBeenCalledTimes(1); 32 | expect(mockAxios.post).toHaveBeenCalledWith(process.env.SLACK_URL, { 33 | text: 'Added Pod nginx@default (version 1)' 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tools/event-watcher/.gitignore: -------------------------------------------------------------------------------- 1 | event-watcher 2 | -------------------------------------------------------------------------------- /tools/event-watcher/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | RUN mkdir /app && apk update && apk add --no-cache git 3 | ADD ./event-watcher.go /app/ 4 | ADD ./go.* /app/ 5 | WORKDIR /app 6 | RUN CGO_ENABLED=0 go build -a -o event-watcher . 7 | 8 | FROM alpine 9 | ENV ONMS_URL="http://localhost:8980/opennms" \ 10 | ONMS_USER="admin" \ 11 | ONMS_PASSWD="admin" 12 | COPY --from=builder /app/event-watcher /usr/local/bin/event-watcher 13 | RUN addgroup -S onms && adduser -S -G onms onms && apk add --no-cache bash tzdata 14 | 15 | USER onms 16 | LABEL maintainer="Alejandro Galue " \ 17 | name="Event Watcher: Send K8s events to OpenNMS" 18 | ENTRYPOINT [ "event-watcher" ] 19 | -------------------------------------------------------------------------------- /tools/event-watcher/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Event Watcher 2 | 3 | This application watches core Kubernetes events and forward them to OpenNMS through its ReST API. 4 | 5 | This is a PoC of having a way to export internal Kubernetes events to OpenNMS. 6 | 7 | The current implementation is very simple and doesn't have the intelligence required to avoid resending the events when the Pod is restarted. Upcoming versions will have this fixed. 8 | 9 | ## Requirements 10 | 11 | * A service account with get, list and watch access to namespaces, pods, services and events. 12 | * `ONMS_URL` environment variable with the base URL of the OpenNMS WebUI. 13 | * `ONMS_USER` environment variable with a user with `ROLE_REST` or `ROLE_ADMIN`. 14 | * `ONMS_PASSWD` environment variable with the password for `ONMS_USER`. 15 | * The events definitions configured in OpenNMS. 16 | 17 | ## Implemented OpenNMS Events 18 | 19 | * uei.opennms.org/kubernetes/pod/ADDED 20 | * uei.opennms.org/kubernetes/pod/DELETED 21 | * uei.opennms.org/kubernetes/service/ADDED 22 | * uei.opennms.org/kubernetes/service/DELETED 23 | * uei.opennms.org/kubernetes/event/Warning 24 | 25 | The first 4 events are straight forward. The last one covers different scenarios of Warning failures detected on Pods like invalid images, resource constraints problems, etc. 26 | 27 | ## Build 28 | 29 | In order to build the application: 30 | 31 | ```bash 32 | docker build -t agalue/onms-k8s-watcher-go:latest . 33 | docker push agalue/onms-k8s-watcher-go:latest 34 | ``` 35 | 36 | > *NOTE*: Please use your own Docker Hub account or use the image provided on my account. 37 | 38 | To build the controller locally for testing: 39 | 40 | ```bash 41 | export GO111MODULE="on" 42 | export ONMS_URL="https://onms.aws.agalue.net/opennms" 43 | 44 | go build 45 | ./event-watcher 46 | ``` 47 | 48 | > *NOTE*: Please use your own GitHub account. 49 | 50 | The controller will use `KUBECONFIG` if the environment variable exist and points to the appropriate configuration file. Otherwise it will assume it is running within Kubernetes. 51 | 52 | ## Permissions 53 | 54 | Do not forget to configure the service account 55 | 56 | ```yaml 57 | --- 58 | apiVersion: v1 59 | kind: ServiceAccount 60 | metadata: 61 | name: event-watcher-user 62 | namespace: opennms 63 | 64 | --- 65 | apiVersion: rbac.authorization.k8s.io/v1 66 | kind: ClusterRole 67 | metadata: 68 | name: event-watcher-role 69 | rules: 70 | - apiGroups: 71 | - "" 72 | resources: 73 | - pods 74 | - services 75 | - events 76 | verbs: 77 | - get 78 | - list 79 | - watch 80 | 81 | --- 82 | apiVersion: rbac.authorization.k8s.io/v1 83 | kind: ClusterRoleBinding 84 | metadata: 85 | name: event-watcher-binding 86 | subjects: 87 | - kind: ServiceAccount 88 | name: event-watcher-user 89 | namespace: opennms 90 | roleRef: 91 | apiGroup: rbac.authorization.k8s.io 92 | kind: ClusterRole 93 | name: event-watcher-role 94 | ``` 95 | -------------------------------------------------------------------------------- /tools/event-watcher/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/agalue/event-watcher 2 | 3 | go 1.16 4 | 5 | require ( 6 | k8s.io/api v0.20.2 7 | k8s.io/apimachinery v0.20.2 8 | k8s.io/client-go v0.20.2 9 | ) 10 | -------------------------------------------------------------------------------- /tools/k8s-to-onms/.gitignore: -------------------------------------------------------------------------------- 1 | k8s-to-onms 2 | -------------------------------------------------------------------------------- /tools/k8s-to-onms/README.md: -------------------------------------------------------------------------------- 1 | # K8s To OpenNMS 2 | 3 | This is a tool to retrieve all the running Pods from a given namespace, generate a requisition and push it to OpenNMS through the ReST API. 4 | 5 | When the Pod has special labels, a Monitored Service will be added to the node definition. 6 | 7 | Currenlty supported labels are: `kafka`, `cassandra`, `postgresql`, `elasticsearch`. 8 | 9 | The script expects the following flags: 10 | 11 | * `-url`, the OpenNMS Base URL. Defaults to `https://onms.aws.agalue.net/opennms`. 12 | * `-user`, the username to access the OpenNMS ReST API. Defaults to `admin`. 13 | * `-passwd`, the password to access the OpenNMS ReST API. Defaults to `admin`. 14 | * `-namespace`, the Kubernetes namespace to analyze. Defaults to `opennms`. 15 | * `-requisition`, the name of the requisition. Defaults to `Kubernetes`. 16 | * `-config`, the full path to the Kubernetes Config to access the cluster. Defaults to `~/.kube/config`. 17 | -------------------------------------------------------------------------------- /tools/k8s-to-onms/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/agalue/k8s-to-onms 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/OpenNMS/onmsctl v1.0.0-beta7 7 | github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect 8 | gopkg.in/yaml.v2 v2.4.0 9 | k8s.io/api v0.20.2 10 | k8s.io/apimachinery v0.20.2 11 | k8s.io/client-go v0.20.2 12 | ) 13 | -------------------------------------------------------------------------------- /tools/k8s-to-onms/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/xml" 5 | "context" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | 11 | "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/client-go/kubernetes" 13 | _ "k8s.io/client-go/plugin/pkg/client/auth" 14 | "k8s.io/client-go/tools/clientcmd" 15 | 16 | "github.com/OpenNMS/onmsctl/model" 17 | "github.com/OpenNMS/onmsctl/rest" 18 | "github.com/OpenNMS/onmsctl/services" 19 | ) 20 | 21 | func main() { 22 | log.SetOutput(os.Stdout) 23 | 24 | flag.StringVar(&rest.Instance.URL, "url", "https://onms.aws.agalue.net/opennms", "OpenNMS URL") 25 | flag.StringVar(&rest.Instance.Username, "user", rest.Instance.Username, "OpenNMS Username") 26 | flag.StringVar(&rest.Instance.Password, "passwd", rest.Instance.Password, "OpenNMS Password") 27 | flag.BoolVar(&rest.Instance.Insecure, "insecure", rest.Instance.Insecure, "Skip Certificate Validation") 28 | namespace := flag.String("namespace", "opennms", "The namespace where the OpenNMS resources live") 29 | location := flag.String("location", "Kubernetes", "The name of the Location for the target nodes") 30 | kubecfg := flag.String("config", os.Getenv("HOME")+"/.kube/config", "Kubernetes Configuration") 31 | show := flag.Bool("show", false, "Only show requisition in YAML") 32 | flag.Parse() 33 | 34 | ctx := context.Background() 35 | 36 | config, err := clientcmd.BuildConfigFromFlags("", *kubecfg) 37 | if err != nil { 38 | panic(err) 39 | } 40 | client, err := kubernetes.NewForConfig(config) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | req := model.Requisition{ 46 | Name: "Kubernetes-NS-" + *namespace, 47 | } 48 | 49 | svcs, err := client.CoreV1().Services(*namespace).List(ctx, v1.ListOptions{}) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | for _, svc := range svcs.Items { 55 | if svc.Spec.ClusterIP != "None" { 56 | node := &model.RequisitionNode{ 57 | ForeignID: "svc-" + svc.Name, 58 | NodeLabel: "svc-" + svc.Name, 59 | Location: *location, 60 | Building: svc.Namespace, 61 | } 62 | intf := &model.RequisitionInterface{ 63 | IPAddress: svc.Spec.ClusterIP, 64 | Description: "ClusterIP", 65 | SnmpPrimary: "N", 66 | Status: 1, 67 | } 68 | for _, p := range svc.Spec.Ports { 69 | name := p.Name 70 | port := fmt.Sprintf("%d", p.Port) 71 | if name == "" { 72 | name = string(p.Protocol) 73 | } 74 | node.AddMetaData("port-"+name, port) 75 | } 76 | node.AddInterface(intf) 77 | for key, value := range svc.ObjectMeta.Labels { 78 | node.AddMetaData(key, value) 79 | } 80 | req.AddNode(node) 81 | } 82 | } 83 | 84 | pods, err := client.CoreV1().Pods(*namespace).List(ctx, v1.ListOptions{}) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | for _, pod := range pods.Items { 90 | isStateful := false 91 | if pod.OwnerReferences != nil && len(pod.OwnerReferences) > 0 { 92 | for _, owner := range pod.OwnerReferences { 93 | if owner.Kind == "StatefulSet" { 94 | isStateful = true 95 | break 96 | } 97 | } 98 | } 99 | if !isStateful { 100 | log.Printf("%s is not part of a StatefulSet, ignoring", pod.Name) 101 | continue 102 | } 103 | node := &model.RequisitionNode{ 104 | ForeignID: "pod-" + pod.Name, 105 | NodeLabel: "pod-" + pod.Name, 106 | Location: *location, 107 | Building: pod.Namespace, 108 | } 109 | intf := &model.RequisitionInterface{ 110 | IPAddress: pod.Status.PodIP, 111 | Description: "Volatile-IP", 112 | SnmpPrimary: "N", 113 | Status: 1, 114 | } 115 | if value, ok := pod.ObjectMeta.Labels["app"]; ok { 116 | switch value { 117 | case "kafka": 118 | intf.AddService(&model.RequisitionMonitoredService{Name: "JMX-Kafka"}) 119 | case "cassandra": 120 | intf.AddService(&model.RequisitionMonitoredService{Name: "JMX-Cassandra"}) 121 | intf.AddService(&model.RequisitionMonitoredService{Name: "JMX-Cassandra-Newts"}) 122 | case "postgres": 123 | intf.AddService(&model.RequisitionMonitoredService{Name: "PostgreSQL"}) 124 | case "elasticsearch": 125 | intf.AddService(&model.RequisitionMonitoredService{Name: "Elasticsearch"}) 126 | case "onms": 127 | loopback := &model.RequisitionInterface{ 128 | IPAddress: "127.0.0.1", 129 | SnmpPrimary: "N", 130 | Status: 1, 131 | Services: []model.RequisitionMonitoredService{ 132 | {Name: "OpenNMS-JVM"}, 133 | }, 134 | } 135 | node.AddInterface(loopback) 136 | } 137 | } 138 | node.AddInterface(intf) 139 | node.AddMetaData("hostIP", pod.Status.HostIP) 140 | for key, value := range pod.ObjectMeta.Labels { 141 | node.AddMetaData(key, value) 142 | } 143 | req.AddNode(node) 144 | if !*show { 145 | log.Printf("adding node for pod %s\n", pod.Name) 146 | } 147 | } 148 | if *show { 149 | xmlBytes, _ := xml.MarshalIndent(&req, "", " ") 150 | log.Printf("Generated requisition:\n%s", string(xmlBytes)) 151 | } else { 152 | svc := services.GetRequisitionsAPI(rest.Instance) 153 | err = svc.SetRequisition(req) 154 | if err != nil { 155 | panic(err) 156 | } 157 | err = svc.ImportRequisition(req.Name, "true") 158 | if err != nil { 159 | panic(err) 160 | } 161 | } 162 | fmt.Println("Done!") 163 | } 164 | -------------------------------------------------------------------------------- /tools/kafka-converter/.gitignore: -------------------------------------------------------------------------------- 1 | kafka-converter 2 | -------------------------------------------------------------------------------- /tools/kafka-converter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | RUN mkdir /app && \ 3 | echo "@edgecommunity http://nl.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \ 4 | apk update && \ 5 | apk add --no-cache build-base git librdkafka-dev@edgecommunity 6 | ADD ./ /app/ 7 | WORKDIR /app 8 | RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags static_all,netgo,musl -o kafka-converter . 9 | 10 | FROM alpine 11 | ENV BOOTSTRAP_SERVERS="localhost:9092" \ 12 | SOURCE_TOPIC="alarms" \ 13 | DEST_TOPIC="alarms-json" \ 14 | DEST_TOPIC_FLAT="" \ 15 | GROUP_ID="opennms" \ 16 | MESSAGE_KIND="alarm" \ 17 | DEBUG="false" 18 | RUN apk add --no-cache bash tzdata && \ 19 | addgroup -S onms && \ 20 | adduser -S -G onms onms 21 | COPY --from=builder /app/kafka-converter /kafka-converter 22 | COPY ./docker-entrypoint.sh / 23 | USER onms 24 | LABEL maintainer="Alejandro Galue " \ 25 | name="OpenNMS Kafka Producer: GPB to JSON Converter" 26 | ENTRYPOINT [ "/docker-entrypoint.sh" ] 27 | -------------------------------------------------------------------------------- /tools/kafka-converter/README.md: -------------------------------------------------------------------------------- 1 | # OpenNMS-Kafka-Converter 2 | 3 | > **DEPRECATED**: This tool is not used anymore as the current solution is based on the [Producer Enhancer](https://github.com/agalue/producer-enhancer). 4 | 5 | A simple Kafka Consumer application to convert GPB payload from Topic A to JSON into Topic B 6 | 7 | This solution requires using the OpenNMS Kafka Producer. This feature can export events, alarms, metrics, nodes and edges from the OpenNMS database to Kafka. All the payloads are stored using Google Protobuf. 8 | 9 | Unfortunately, for certain solution like Serverless, JSON (or to be more precise, plain text) is required in order to use Kafka as a trigger once a new message arrive to a given Topic. This is why this tool has been implemented. 10 | 11 | This repository also contains a Dockerfile to compile and build an image with the tool, which can be fully customized through environment variables, so the solution can be used with Kubernetes (the sample YAML file is also available). 12 | 13 | ## Requirements 14 | 15 | * `BOOTSTRAP_SERVERS` environment variable with Kafka Bootstrap Server (i.e. `kafka01:9092`) 16 | * `SOURCE_TOPIC` environment variable with the source Kafka Topic with GPB Payload 17 | * `DEST_TOPIC` environment variable with the destination Kafka Topic with JSON Payload 18 | * `GROUP_ID` \[Optional\] environment variable with the Consumer Group ID (defaults to `opennms`) 19 | * `MESSAGE_KIND` \[Optional\] environment variable with the payload type. Valid values are: alarm, event, node, metric, edge (defaults to `alarm`). 20 | * To pass producer settings, add an environment variable with the prefix `PRODUCER_`, for example: `PROCUCER_MAX_REQUEST_SIZE`. 21 | * To pass consumer settings, add an environment variable with the prefix `CONSUMER_`, for example: `CONSUMER_AUTO_OFFSET_RESET`. 22 | 23 | For producer/consumer settings, the character "_" will be replaced with "." and converted to lowercase. For example, `CONSUMER_AUTO_OFFSET_RESET` will be configured as `auto.offset.reset`. 24 | 25 | ## Build 26 | 27 | In order to build the application: 28 | 29 | ```bash 30 | docker build -t agalue/kafka-converter-go:latest . 31 | docker push agalue/kafka-converter-go:latest 32 | ``` 33 | 34 | > *NOTE*: Please use your own Docker Hub account or use the image provided on my account. 35 | 36 | To build the controller locally for testing: 37 | 38 | ```bash 39 | export GO111MODULE="on" 40 | 41 | go build 42 | ./kafka-converter 43 | ``` 44 | -------------------------------------------------------------------------------- /tools/kafka-converter/api/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | type protoc >/dev/null 2>&1 || { echo >&2 "protoc required but it's not installed; aborting."; exit 1; } 4 | 5 | protoc -I . opennms-kafka-producer.proto --go_out=./producer 6 | protoc -I . collectionset.proto --go_out=./producer 7 | -------------------------------------------------------------------------------- /tools/kafka-converter/api/collectionset.proto: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/OpenNMS/opennms/blob/master/features/kafka/producer/src/main/proto/collectionset.proto 2 | 3 | syntax = "proto3"; 4 | 5 | option go_package = ".;producer"; 6 | 7 | message StringAttribute { 8 | string name = 1; 9 | string value = 2; 10 | } 11 | 12 | message NumericAttribute { 13 | string group = 1; 14 | string name = 2; 15 | double value = 3; 16 | enum Type { 17 | GAUGE = 0; 18 | COUNTER = 1; 19 | } 20 | Type type = 4; 21 | } 22 | 23 | message NodeLevelResource { 24 | int64 node_id = 1; 25 | string foreign_source = 2; 26 | string foreign_id= 3; 27 | string node_label = 4; 28 | string location = 5; 29 | } 30 | 31 | message InterfaceLevelResource { 32 | NodeLevelResource node = 1; 33 | string instance = 2; 34 | } 35 | 36 | message GenericTypeResource { 37 | NodeLevelResource node = 1; 38 | string type = 2; 39 | string instance = 3; 40 | } 41 | 42 | message ResponseTimeResource { 43 | string instance = 1; 44 | string location = 2; 45 | } 46 | 47 | message CollectionSetResource { 48 | oneof resource { 49 | NodeLevelResource node = 1; 50 | InterfaceLevelResource interface = 2; 51 | GenericTypeResource generic = 3; 52 | ResponseTimeResource response = 4; 53 | } 54 | repeated StringAttribute string = 10; 55 | repeated NumericAttribute numeric = 11; 56 | } 57 | 58 | message CollectionSet { 59 | int64 timestamp = 1; 60 | repeated CollectionSetResource resource = 2; 61 | } 62 | -------------------------------------------------------------------------------- /tools/kafka-converter/api/opennms-kafka-producer.proto: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/OpenNMS/opennms/blob/master/features/kafka/producer/src/main/proto/opennms-kafka-producer.proto 2 | 3 | syntax = "proto3"; 4 | 5 | option go_package = ".;producer"; 6 | 7 | // The values differ from the standard codes in OpenNMS 8 | // since proto3 enforces us to start at 0 9 | enum Severity { 10 | INDETERMINATE = 0; 11 | CLEARED = 1; 12 | NORMAL = 2; 13 | WARNING = 3; 14 | MINOR = 4; 15 | MAJOR = 5; 16 | CRITICAL = 6; 17 | } 18 | 19 | message NodeCriteria { 20 | uint64 id = 1; 21 | string foreign_source = 2; 22 | string foreign_id = 3; 23 | } 24 | 25 | message EventParameter { 26 | string name = 1; 27 | string value = 2; 28 | string type = 3; 29 | } 30 | 31 | message Event { 32 | uint64 id = 1; 33 | string uei = 2; 34 | string label = 3; 35 | uint64 time = 4; 36 | string source = 5; 37 | repeated EventParameter parameter = 6; 38 | uint64 create_time = 7; 39 | string description = 8; 40 | string log_message = 9; 41 | Severity severity = 10; 42 | bool log = 11; 43 | bool display = 12; 44 | NodeCriteria node_criteria = 13; 45 | string ip_address = 14; 46 | } 47 | 48 | message Alarm { 49 | uint64 id = 1; 50 | string uei = 2; 51 | NodeCriteria node_criteria = 3; 52 | string ip_address = 4; 53 | string service_name = 5; 54 | string reduction_key = 6; 55 | enum Type { 56 | PROBLEM_WITH_CLEAR = 0; 57 | CLEAR = 1; 58 | PROBLEM_WITHOUT_CLEAR = 2; 59 | } 60 | Type type = 7; 61 | uint64 count = 8; 62 | Severity severity = 9; 63 | uint64 first_event_time = 10; 64 | string description = 11; 65 | string log_message = 12; 66 | string ack_user = 13; 67 | uint64 ack_time = 14; 68 | Event last_event = 15; 69 | uint64 last_event_time = 16; 70 | uint32 if_index = 17; 71 | string operator_instructions = 18; 72 | string clear_key = 19; 73 | string managed_object_instance = 20; 74 | string managed_object_type = 21; 75 | repeated Alarm relatedAlarm = 22; 76 | } 77 | 78 | message AlarmFeedback { 79 | string situation_key = 1; 80 | string situation_fingerprint = 2; 81 | string alarm_key = 3; 82 | enum FeedbackType { 83 | FALSE_POSITIVE = 0; 84 | FALSE_NEGATIVE = 1; 85 | CORRECT = 2; 86 | UNKNOWN = 3; 87 | } 88 | FeedbackType feedback_type = 4; 89 | string reason = 5; 90 | string user = 6; 91 | uint64 timestamp = 7; 92 | } 93 | 94 | message IpInterface { 95 | uint64 id = 1; 96 | string ip_address = 2; 97 | uint32 if_index = 3; 98 | enum PrimaryType { 99 | PRIMARY = 0; 100 | SECONDARY = 1; 101 | NOT_ELIGIBLE = 2; 102 | } 103 | PrimaryType primary_type = 4; 104 | repeated string service = 5; 105 | } 106 | 107 | message SnmpInterface { 108 | uint64 id = 1; 109 | uint32 if_index = 2; 110 | string if_descr = 3; 111 | uint32 if_type = 4; 112 | string if_name = 5; 113 | uint64 if_speed = 6; 114 | string if_phys_address = 7; 115 | uint32 if_admin_status = 8; 116 | uint32 if_oper_status = 9; 117 | string if_alias = 10; 118 | } 119 | 120 | message HwAlias { 121 | uint32 index = 1; 122 | string oid = 2; 123 | } 124 | 125 | message HwEntity { 126 | uint32 ent_physical_index = 1; 127 | uint32 entity_id = 2; 128 | string ent_physical_class = 3; 129 | string ent_physical_descr = 4; 130 | bool ent_physical_is_fru = 5; 131 | string ent_physical_name = 6; 132 | string ent_physical_vendor_type = 7; 133 | repeated HwAlias ent_hw_alias = 8; 134 | repeated HwEntity children = 9; 135 | } 136 | 137 | message Node { 138 | uint64 id = 1; 139 | string foreign_source = 2; 140 | string foreign_id = 3; 141 | string location = 4; 142 | repeated string category = 5; 143 | string label = 6; 144 | uint64 create_time = 7; 145 | string sys_contact = 8; 146 | string sys_description = 9; 147 | string sys_object_id = 10; 148 | repeated IpInterface ip_interface = 11; 149 | repeated SnmpInterface snmp_interface = 12; 150 | HwEntity hw_inventory = 13; 151 | } 152 | 153 | message TopologyRef { 154 | string id = 1; 155 | enum Protocol { 156 | LLDP = 0; 157 | OSPF = 1; 158 | ISIS = 2; 159 | BRIDGE = 3; 160 | CDP = 4; 161 | USERDEFINED = 5; 162 | } 163 | Protocol protocol = 2; 164 | } 165 | 166 | message TopologySegment { 167 | TopologyRef ref = 1; 168 | } 169 | 170 | message TopologyPort { 171 | string vertex_id = 1; 172 | uint64 if_index = 2; 173 | string if_name = 3; 174 | string address = 4; 175 | NodeCriteria node_criteria = 5; 176 | } 177 | 178 | message TopologyEdge { 179 | TopologyRef ref = 1; 180 | oneof source { 181 | TopologyPort sourcePort = 3; 182 | TopologySegment sourceSegment = 4; 183 | Node sourceNode = 5; 184 | } 185 | oneof target { 186 | TopologyPort targetPort = 6; 187 | TopologySegment targetSegment = 7; 188 | Node targetNode = 8; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tools/kafka-converter/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | join() { 4 | local IFS="$1"; shift; echo "$*"; 5 | } 6 | 7 | get_key() { 8 | echo "$1" | cut -d_ -f2- | tr '[:upper:]' '[:lower:]' | tr _ . 9 | } 10 | 11 | IFS=$'\n' 12 | PRODUCER=() 13 | CONSUMER=("acks=1") 14 | for VAR in $(env) 15 | do 16 | env_var=$(echo "$VAR" | cut -d= -f1) 17 | if [[ $env_var =~ ^CONSUMER_ ]]; then 18 | echo "[configuring consumer] processing $env_var" 19 | key=$(get_key $env_var) 20 | echo "[configuring consumer] key: $key" 21 | val=${!env_var} 22 | echo "[configuring consumer] value: $val" 23 | CONSUMER+=("$key=$val") 24 | fi 25 | if [[ $env_var =~ ^PRODUCER_ ]]; then 26 | echo "[configuring producer] processing $env_var" 27 | key=$(get_key $env_var) 28 | echo "[configuring producer] key: $key" 29 | val=${!env_var} 30 | echo "[configuring producer] '$key'='$val'" 31 | PRODUCDER+=("$key=$val") 32 | fi 33 | done 34 | 35 | exec /kafka-converter \ 36 | -bootstrap "${BOOTSTRAP_SERVERS}" \ 37 | -source-topic "${SOURCE_TOPIC}" \ 38 | -dest-topic "${DEST_TOPIC}" \ 39 | -dest-topic-flat "${DEST_TOPIC_FLAT}" \ 40 | -group-id "${GROUP_ID-opennms}" \ 41 | -message-kind "${MESSAGE_KIND-alarm}" \ 42 | -producer-params "$(join , ${PRODUCER[@]})" \ 43 | -consumer-params "$(join , ${CONSUMER[@]})" \ 44 | -debug ${DEBUG} -------------------------------------------------------------------------------- /tools/kafka-converter/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/agalue/kafka-converter 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/confluentinc/confluent-kafka-go v1.7.0 7 | github.com/golang/protobuf v1.5.2 8 | github.com/jeremywohl/flatten v1.0.1 9 | google.golang.org/protobuf v1.27.1 10 | ) 11 | -------------------------------------------------------------------------------- /tools/kafka-converter/go.sum: -------------------------------------------------------------------------------- 1 | github.com/confluentinc/confluent-kafka-go v1.7.0 h1:tXh3LWb2Ne0WiU3ng4h5qiGA9XV61rz46w60O+cq8bM= 2 | github.com/confluentinc/confluent-kafka-go v1.7.0/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= 3 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 4 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 5 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 6 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 7 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 8 | github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= 9 | github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= 10 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 11 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 12 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 13 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 14 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 15 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 16 | -------------------------------------------------------------------------------- /tools/kafka-converter/mock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/agalue/kafka-converter/api/producer" 5 | "github.com/confluentinc/confluent-kafka-go/kafka" 6 | "github.com/golang/protobuf/proto" 7 | ) 8 | 9 | func main() { 10 | alarm := &producer.Alarm{ 11 | Id: 1, 12 | Uei: "uei.opennms.org/test", 13 | NodeCriteria: &producer.NodeCriteria{ 14 | Id: 1, 15 | }, 16 | } 17 | alarmBytes, err := proto.Marshal(alarm) 18 | if err != nil { 19 | panic(err) 20 | } 21 | config := &kafka.ConfigMap{"bootstrap.servers": "127.0.0.1:9092"} 22 | kafkaProducer, err := kafka.NewProducer(config) 23 | topic := "OpenNMS-alarms" 24 | if err != nil { 25 | panic(err) 26 | } 27 | kafkaProducer.Produce(&kafka.Message{ 28 | TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny}, 29 | Value: alarmBytes, 30 | }, nil) 31 | kafkaProducer.Close() 32 | } 33 | --------------------------------------------------------------------------------