docs: README i18n (ja/ko/es/fr/de) + unified logging config (#100)

* feat: unify logging config for frontier and frontlas

Add a shared Log config struct in pkg/config/log.go that drives both
klog and armorigo through a single configuration layer. This fixes the
asymmetry where frontier had --loglevel but frontlas did not, and enables
file-based logging with rotation for non-container deployments.

- New pkg/config/log.go: Log/LogFile structs, SetupLogging(), env overrides
- Support four output modes: stdout, stderr, file, both (stdout+file)
- Support env var overrides: LOG_LEVEL, LOG_OUTPUT, LOG_FORMAT, LOG_FILE
- Add lumberjack dependency for file log rotation
- Both binaries now accept: --loglevel, --log-output, --log-format, --log-file
- Add log section to frontier_all.yaml and frontlas_all.yaml
- Backward compatible: default behavior unchanged (stdout + info + text)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add README translations for ja, ko, es, fr, de

Extend the README language switcher from English/简体中文 to seven
languages and add full translations of the English README for
Japanese, Korean, Spanish, French, and German. Code blocks,
commands, configs, and URLs are preserved verbatim; only prose,
headings, table cells, and captions are translated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Austin Zhai
2026-04-20 08:55:55 +08:00
committed by GitHub
parent 211cedaca6
commit 6576b531a2
16 changed files with 2772 additions and 243 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
English | [简体中文](./README_zh.md)
English | [简体中文](./README_zh.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Español](./README_es.md) | [Français](./README_fr.md) | [Deutsch](./README_de.md)
</div>
+464
View File
@@ -0,0 +1,464 @@
<p align=center>
<img src="./docs/diagram/frontier-logo.png" width="30%">
</p>
<div align="center">
[![Go](https://github.com/singchia/frontier/actions/workflows/go.yml/badge.svg)](https://github.com/singchia/frontier/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/singchia/frontier)](https://goreportcard.com/report/github.com/singchia/frontier)
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[English](./README.md) | [简体中文](./README_zh.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Español](./README_es.md) | [Français](./README_fr.md) | Deutsch
</div>
Frontier ist ein in Go geschriebenes, **vollduplexfähiges** Open-Source-Gateway für Langzeitverbindungen. Es ermöglicht Microservices, Edge-Knoten oder Clients direkt zu erreichen und umgekehrt. Es bietet vollduplexfähige **bidirektionale RPCs**, **Messaging** und **Punkt-zu-Punkt-Streams**. Frontier folgt den Prinzipien einer **Cloud-Native**-Architektur, unterstützt eine schnelle Cluster-Bereitstellung über den Operator und ist für **Hochverfügbarkeit** und **elastische Skalierung** auf Millionen aktiver Edge-Knoten oder Clients ausgelegt.
## Inhaltsverzeichnis
- [Funktionen](#funktionen)
- [Schnellstart](#schnellstart)
- [Architektur](#architektur)
- [Verwendung](#verwendung)
- [Konfiguration](#konfiguration)
- [Bereitstellung](#bereitstellung)
- [Cluster](#cluster)
- [Kubernetes](#kubernetes)
- [Entwicklung](#entwicklung)
- [Tests](#tests)
- [Community](#community)
- [Lizenz](#lizenz)
## Schnellstart
1. Eine einzelne Frontier-Instanz starten:
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
2. Beispiele bauen und ausführen:
```bash
make examples
```
Das Chatroom-Beispiel ausführen:
```bash
# Terminal 1
./bin/chatroom_service
# Terminal 2
./bin/chatroom_agent
```
Demovideo:
https://github.com/singchia/frontier/assets/15531166/18b01d96-e30b-450f-9610-917d65259c30
## Funktionen
- **Bidirektionale RPCs**: Services und Edges können sich gegenseitig aufrufen, inklusive Lastverteilung.
- **Messaging**: Topic-basiertes Publish/Receive zwischen Services, Edges und externem MQ.
- **Punkt-zu-Punkt-Streams**: direkte Streams für Proxying, Dateiübertragung und benutzerdefinierten Verkehr.
- **Cloud-Native-Bereitstellung**: Ausführung über Docker, Compose, Helm oder Operator.
- **Hochverfügbarkeit und Skalierung**: Unterstützung für Reconnect, Clustering und horizontale Skalierung mit Frontlas.
- **Authentifizierung und Präsenz**: Edge-Authentifizierung sowie Online-/Offline-Benachrichtigungen.
- **Control-Plane-APIs**: gRPC- und REST-APIs zum Abfragen und Verwalten aktiver Knoten.
## Architektur
**Frontier-Komponente**
<img src="./docs/diagram/frontier.png" width="100%">
- _Service End_: Einstiegspunkt für Microservice-Funktionen, standardmäßig verbunden.
- _Edge End_: Einstiegspunkt für Edge-Knoten- oder Client-Funktionen.
- _Publish/Receive_: Nachrichten veröffentlichen und empfangen.
- _Call/Register_: Funktionen aufrufen und registrieren.
- _OpenStream/AcceptStream_: Öffnen und Annehmen von Punkt-zu-Punkt-Streams (Verbindungen).
- _External MQ_: Frontier unterstützt gemäß Konfiguration die Weiterleitung von Nachrichten, die von Edge-Knoten veröffentlicht werden, an Topics eines externen MQ.
Frontier verlangt, dass sich sowohl Microservices als auch Edge-Knoten aktiv zu Frontier verbinden. Beim Verbindungsaufbau können Metadaten von Service und Edge (Empfangs-Topics, RPC, Service-Namen etc.) mitgegeben werden. Die Standardverbindungsports sind:
- :30011: Microservices verbinden sich darüber und erhalten Service.
- :30012: Edge-Knoten verbinden sich darüber und erhalten Edge.
- :30010: für Betriebsmitarbeiter oder Programme zur Nutzung der Control Plane.
### Funktionalität
<table><thead>
<tr>
<th>Funktion</th>
<th>Initiator</th>
<th>Empfänger</th>
<th>Methode</th>
<th>Routing-Verfahren</th>
<th>Beschreibung</th>
</tr></thead>
<tbody>
<tr>
<td rowspan="2">Messager</td>
<td>Service</td>
<td>Edge</td>
<td>Publish</td>
<td>EdgeID+Topic</td>
<td>Muss an eine bestimmte EdgeID veröffentlicht werden, das Standard-Topic ist leer. Der Edge ruft Receive auf, um die Nachricht zu empfangen, und muss nach der Verarbeitung msg.Done() oder msg.Error(err) aufrufen, um die Nachrichtenkonsistenz sicherzustellen.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service oder External MQ</td>
<td>Publish</td>
<td>Topic</td>
<td>Muss an ein Topic veröffentlicht werden; Frontier wählt anhand des Topics einen konkreten Service oder MQ aus.</td>
</tr>
<tr>
<td rowspan="2">RPCer</td>
<td>Service</td>
<td>Edge</td>
<td>Call</td>
<td>EdgeID+Method</td>
<td>Muss eine bestimmte EdgeID aufrufen und dabei den Methodennamen mitführen.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>Call</td>
<td>Method</td>
<td>Muss eine Methode aufrufen; Frontier wählt anhand des Methodennamens einen konkreten Service aus.</td>
</tr>
<tr>
<td rowspan="2">Multiplexer</td>
<td>Service</td>
<td>Edge</td>
<td>OpenStream</td>
<td>EdgeID</td>
<td>Muss einen Stream zu einer bestimmten EdgeID öffnen.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>OpenStream</td>
<td>ServiceName</td>
<td>Muss einen Stream zu einem ServiceName öffnen, der bei der Service-Initialisierung über service.OptionServiceName angegeben wird.</td>
</tr>
</tbody></table>
**Zentrale Designprinzipien**:
1. Alle Nachrichten, RPCs und Streams sind Punkt-zu-Punkt-Übertragungen.
- Von Microservices zu Edges muss die Edge-Knoten-ID angegeben werden.
- Von Edges zu Microservices routet Frontier anhand von Topic und Method und wählt schließlich per Hashing einen Microservice oder externen MQ aus. Standardmäßig wird auf Basis von edgeid gehasht, wahlweise auch random oder srcip.
2. Nachrichten erfordern eine explizite Bestätigung durch den Empfänger.
- Zur Sicherstellung der Zustellsemantik muss der Empfänger msg.Done() oder msg.Error(err) aufrufen, um die Zustellkonsistenz zu gewährleisten.
3. Vom Multiplexer geöffnete Streams repräsentieren logisch eine direkte Kommunikation zwischen Microservices und Edge-Knoten.
- Sobald die Gegenseite den Stream empfängt, erreichen alle Funktionen auf diesem Stream die Gegenseite direkt und umgehen die Routing-Richtlinien von Frontier.
## Verwendung
Ausführliche Nutzungsanleitung: [docs/USAGE.md](./docs/USAGE.md)
## Konfiguration
Ausführliche Konfigurationsanleitung: [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
## Bereitstellung
Bei einer einzelnen Frontier-Instanz können Sie Ihre Instanz je nach Umgebung mit den folgenden Methoden bereitstellen.
### Docker
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
### Docker-Compose
```bash
git clone https://github.com/singchia/frontier.git
cd dist/compose
docker-compose up -d frontier
```
### Helm
In einer Kubernetes-Umgebung können Sie mit Helm schnell eine Instanz bereitstellen.
```bash
git clone https://github.com/singchia/frontier.git
cd dist/helm
helm install frontier ./ -f values.yaml
```
Ihr Microservice sollte sich mit ```service/frontier-servicebound-svc:30011``` verbinden, und Ihr Edge-Knoten kann sich mit dem NodePort verbinden, auf dem `:30012` liegt.
### Systemd
Nutzen Sie die separate Systemd-Dokumentation:
[dist/systemd/README.md](./dist/systemd/README.md)
### Operator
Siehe den Abschnitt zur Cluster-Bereitstellung weiter unten.
## Cluster
### Frontier + Frontlas-Architektur
<img src="./docs/diagram/frontlas.png" width="100%">
Die zusätzliche Frontlas-Komponente dient zum Aufbau des Clusters. Frontlas ist ebenfalls zustandslos und speichert keine weiteren Informationen im Speicher, weshalb eine zusätzliche Abhängigkeit zu Redis erforderlich ist. Sie müssen Frontlas Redis-Verbindungsdaten bereitstellen; unterstützt werden `redis`, `sentinel` und `redis-cluster`.
- _Frontier_: Kommunikationskomponente zwischen Microservices und der Edge-Datenebene.
- _Frontlas_: kurz für Frontier Atlas, eine Cluster-Management-Komponente, die Metadaten und Aktivitätsinformationen von Microservices und Edges in Redis ablegt.
Frontier muss sich proaktiv mit Frontlas verbinden, um seinen eigenen Status sowie die Aktivität und den Status von Microservices und Edges zu melden. Die Standardports von Frontlas sind:
- `:40011` für Microservice-Verbindungen, ersetzt den Port 30011 einer einzelnen Frontier-Instanz.
- `:40012` für die Verbindung von Frontier zur Statusmeldung.
Sie können beliebig viele Frontier-Instanzen bereitstellen; für Frontlas genügt es, zwei Instanzen getrennt bereitzustellen, um HA (Hochverfügbarkeit) zu gewährleisten, da es keinen Zustand speichert und keine Konsistenzprobleme aufweist.
### Konfiguration
In der `frontier.yaml` von **Frontier** muss folgende Konfiguration ergänzt werden:
```yaml
frontlas:
enable: true
dial:
network: tcp
addr:
- 127.0.0.1:40012
metrics:
enable: false
interval: 0
daemon:
# Innerhalb des Frontier-Clusters eindeutige ID
frontier_id: frontier01
```
Frontier muss sich mit Frontlas verbinden, um seinen eigenen Status sowie den von Microservices und Edges zu melden.
Minimale Konfiguration der `frontlas.yaml` von **Frontlas**:
```yaml
control_plane:
listen:
# Microservices verbinden sich mit dieser Adresse, um Edges im Cluster zu entdecken
network: tcp
addr: 0.0.0.0:40011
frontier_plane:
# Frontier verbindet sich mit dieser Adresse
listen:
network: tcp
addr: 0.0.0.0:40012
expiration:
# Ablaufzeit der Microservice-Metadaten in Redis
service_meta: 30
# Ablaufzeit der Edge-Metadaten in Redis
edge_meta: 30
redis:
# Unterstützt Standalone-, Sentinel- und Cluster-Verbindungen
mode: standalone
standalone:
network: tcp
addr: redis:6379
db: 0
```
### Verwendung
Da Frontlas dazu dient, verfügbare Frontiers zu finden, müssen sich Microservices folgendermaßen anpassen:
**Microservice bezieht Service**
```golang
package main
import (
"net"
"github.com/singchia/frontier/api/dataplane/v1/service"
)
func main() {
// NewClusterService verwenden, um Service zu beziehen
svc, err := service.NewClusterService("127.0.0.1:40011")
// Service verwenden, alles andere bleibt unverändert
}
```
**Edge-Knoten erhält die Verbindungsadresse**
Edge-Knoten verbinden sich weiterhin mit Frontier, können aber verfügbare Frontier-Adressen von Frontlas erhalten. Frontlas stellt eine Schnittstelle zum Auflisten der Frontier-Instanzen bereit:
```bash
curl -X GET http://127.0.0.1:40011/cluster/v1/frontiers
```
Sie können diese Schnittstelle kapseln, um den Edge-Knoten Lastverteilung oder Hochverfügbarkeit bereitzustellen, oder mTLS ergänzen, um sie den Edge-Knoten direkt zur Verfügung zu stellen (nicht empfohlen).
gRPC für die Control Plane siehe [Protobuf-Definition](./api/controlplane/frontlas/v1/cluster.proto).
Die Control Plane von Frontlas unterscheidet sich von der von Frontier, da sie cluster-orientiert ist und derzeit nur lesende Schnittstellen für den Cluster bereitstellt.
```protobuf
service ClusterService {
rpc GetFrontierByEdge(GetFrontierByEdgeIDRequest) returns (GetFrontierByEdgeIDResponse);
rpc ListFrontiers(ListFrontiersRequest) returns (ListFrontiersResponse);
rpc ListEdges(ListEdgesRequest) returns (ListEdgesResponse);
rpc GetEdgeByID(GetEdgeByIDRequest) returns (GetEdgeByIDResponse);
rpc GetEdgesCount(GetEdgesCountRequest) returns (GetEdgesCountResponse);
rpc ListServices(ListServicesRequest) returns (ListServicesResponse);
rpc GetServiceByID(GetServiceByIDRequest) returns (GetServiceByIDResponse);
rpc GetServicesCount(GetServicesCountRequest) returns (GetServicesCountResponse);
}
```
## Kubernetes
### Operator
**CRD und Operator installieren**
Folgen Sie diesen Schritten, um den Operator in Ihrer .kubeconfig-Umgebung zu installieren und bereitzustellen:
```bash
git clone https://github.com/singchia/frontier.git
cd dist/crd
kubectl apply -f install.yaml
```
CRD prüfen:
```bash
kubectl get crd frontierclusters.frontier.singchia.io
```
Operator prüfen:
```bash
kubectl get all -n frontier-system
```
**FrontierCluster**
```yaml
apiVersion: frontier.singchia.io/v1alpha1
kind: FrontierCluster
metadata:
labels:
app.kubernetes.io/name: frontiercluster
app.kubernetes.io/managed-by: kustomize
name: frontiercluster
spec:
frontier:
# Frontier als einzelne Instanz
replicas: 2
# Port auf der Microservice-Seite
servicebound:
port: 30011
# Port auf der Edge-Knoten-Seite
edgebound:
port: 30012
frontlas:
# Frontlas als einzelne Instanz
replicas: 1
# Port der Control Plane
controlplane:
port: 40011
redis:
# Konfiguration des abhängigen Redis
addrs:
- rfs-redisfailover:26379
password: your-password
masterName: mymaster
redisType: sentinel
```
Als `frontiercluster.yaml` speichern und
```
kubectl apply -f frontiercluster.yaml
```
Innerhalb einer Minute steht Ihnen ein Cluster mit 2 Frontier-Instanzen + 1 Frontlas-Instanz zur Verfügung.
Den Bereitstellungsstatus der Ressourcen prüfen Sie mit:
```bash
kubectl get all -l app=frontiercluster-frontier
kubectl get all -l app=frontiercluster-frontlas
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontier-57d565c89-dn6n8 1/1 Running 0 7m22s
pod/frontiercluster-frontier-57d565c89-nmwmt 1/1 Running 0 7m22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-edgebound-svc NodePort 10.233.23.174 <none> 30012:30012/TCP 8m7s
service/frontiercluster-servicebound-svc ClusterIP 10.233.29.156 <none> 30011/TCP 8m7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontier 2/2 2 2 7m22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontier-57d565c89 2 2 2 7m22s
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontlas-85c4fb6d9b-5clkh 1/1 Running 0 8m11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-frontlas-svc ClusterIP 10.233.0.23 <none> 40011/TCP,40012/TCP 8m11s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontlas 1/1 1 1 8m11s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontlas-85c4fb6d9b 1 1 1 8m11s
```
Ihr Microservice sollte sich mit `service/frontiercluster-frontlas-svc:40011` verbinden, und Ihr Edge-Knoten kann sich mit dem NodePort verbinden, auf dem `:30012` liegt.
## Entwicklung
### Roadmap
Siehe [ROADMAP](./ROADMAP.md).
### Beiträge
Wenn Sie einen Fehler finden, öffnen Sie bitte ein Issue; die Projekt-Maintainer antworten zeitnah.
Wenn Sie Features einreichen oder Projektprobleme schneller lösen möchten, sind PRs unter diesen einfachen Bedingungen willkommen:
- Der Code-Stil bleibt konsistent
- Jeder Commit enthält ein Feature
- Der eingereichte Code enthält Unit-Tests
## Tests
### Stream-Funktion
<img src="./docs/diagram/stream.png" width="100%">
## Community
<p align=center>
<img src="./docs/diagram/wechat.JPG" width="30%">
</p>
Treten Sie unserer WeChat-Gruppe bei, um zu diskutieren und Unterstützung zu erhalten.
## Lizenz
Veröffentlicht unter der [Apache License 2.0](https://github.com/singchia/geminio/blob/main/LICENSE).
---
Ein Stern ⭐️ wäre sehr willkommen ♥️
+1 -1
View File
@@ -9,7 +9,7 @@
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
English | [简体中文](./README_zh.md)
English | [简体中文](./README_zh.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Español](./README_es.md) | [Français](./README_fr.md) | [Deutsch](./README_de.md)
</div>
+464
View File
@@ -0,0 +1,464 @@
<p align=center>
<img src="./docs/diagram/frontier-logo.png" width="30%">
</p>
<div align="center">
[![Go](https://github.com/singchia/frontier/actions/workflows/go.yml/badge.svg)](https://github.com/singchia/frontier/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/singchia/frontier)](https://goreportcard.com/report/github.com/singchia/frontier)
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[English](./README.md) | [简体中文](./README_zh.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | Español | [Français](./README_fr.md) | [Deutsch](./README_de.md)
</div>
Frontier es una pasarela de conexión persistente de **full-dúplex** y código abierto, escrita en Go. Permite que los microservicios lleguen directamente a los nodos de borde o clientes, y viceversa. Ofrece **RPC bidireccional** full-dúplex, **mensajería** y **flujos punto a punto**. Frontier sigue los principios de arquitectura **cloud-native**, admite un despliegue rápido de clústeres mediante Operator y está diseñado para **alta disponibilidad** y **escalado elástico** hasta millones de nodos de borde o clientes en línea.
## Tabla de contenidos
- [Características](#características)
- [Inicio rápido](#inicio-rápido)
- [Arquitectura](#arquitectura)
- [Uso](#uso)
- [Configuración](#configuración)
- [Despliegue](#despliegue)
- [Clúster](#clúster)
- [Kubernetes](#kubernetes)
- [Desarrollo](#desarrollo)
- [Pruebas](#pruebas)
- [Comunidad](#comunidad)
- [Licencia](#licencia)
## Inicio rápido
1. Ejecutar una única instancia de Frontier:
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
2. Compilar y ejecutar los ejemplos:
```bash
make examples
```
Ejecutar el ejemplo de sala de chat:
```bash
# Terminal 1
./bin/chatroom_service
# Terminal 2
./bin/chatroom_agent
```
Vídeo de demostración:
https://github.com/singchia/frontier/assets/15531166/18b01d96-e30b-450f-9610-917d65259c30
## Características
- **RPC bidireccional**: servicios y bordes pueden invocarse entre sí con balanceo de carga.
- **Mensajería**: publicación/recepción basada en temas entre servicios, bordes y MQ externo.
- **Flujos punto a punto**: apertura de flujos directos para proxy, transferencia de archivos y tráfico personalizado.
- **Despliegue cloud-native**: ejecución mediante Docker, Compose, Helm u Operator.
- **Alta disponibilidad y escalado**: soporte de reconexión, clustering y escalado horizontal con Frontlas.
- **Autenticación y presencia**: autenticación de bordes y notificaciones de conexión/desconexión.
- **APIs del plano de control**: APIs gRPC y REST para consultar y gestionar los nodos en línea.
## Arquitectura
**Componente Frontier**
<img src="./docs/diagram/frontier.png" width="100%">
- _Service End_: el punto de entrada para funciones de microservicios, que se conecta por defecto.
- _Edge End_: el punto de entrada para funciones de nodos de borde o clientes.
- _Publish/Receive_: publicación y recepción de mensajes.
- _Call/Register_: invocación y registro de funciones.
- _OpenStream/AcceptStream_: apertura y aceptación de flujos punto a punto (conexiones).
- _External MQ_: Frontier admite el reenvío, según la configuración, de los mensajes publicados desde los nodos de borde hacia temas de MQ externos.
Frontier requiere que tanto los microservicios como los nodos de borde se conecten activamente a Frontier. Durante la conexión se pueden transportar los metadatos de Service y Edge (temas de recepción, RPC, nombres de servicio, etc.). Los puertos de conexión por defecto son:
- :30011: para que los microservicios se conecten y obtengan Service.
- :30012: para que los nodos de borde se conecten y obtengan Edge.
- :30010: para que el personal de operaciones o los programas utilicen el plano de control.
### Funcionalidad
<table><thead>
<tr>
<th>Función</th>
<th>Iniciador</th>
<th>Receptor</th>
<th>Método</th>
<th>Método de enrutamiento</th>
<th>Descripción</th>
</tr></thead>
<tbody>
<tr>
<td rowspan="2">Messager</td>
<td>Service</td>
<td>Edge</td>
<td>Publish</td>
<td>EdgeID+Topic</td>
<td>Debe publicar a un EdgeID específico; el tema por defecto está vacío. El borde llama a Receive para recibir el mensaje y, tras procesarlo, debe llamar a msg.Done() o msg.Error(err) para garantizar la consistencia del mensaje.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service o External MQ</td>
<td>Publish</td>
<td>Topic</td>
<td>Debe publicar a un tema; Frontier selecciona un Service o MQ concreto según el tema.</td>
</tr>
<tr>
<td rowspan="2">RPCer</td>
<td>Service</td>
<td>Edge</td>
<td>Call</td>
<td>EdgeID+Method</td>
<td>Debe invocar a un EdgeID específico, indicando el nombre del método.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>Call</td>
<td>Method</td>
<td>Debe invocar un método; Frontier selecciona un Service concreto según el nombre del método.</td>
</tr>
<tr>
<td rowspan="2">Multiplexer</td>
<td>Service</td>
<td>Edge</td>
<td>OpenStream</td>
<td>EdgeID</td>
<td>Debe abrir un flujo hacia un EdgeID específico.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>OpenStream</td>
<td>ServiceName</td>
<td>Debe abrir un flujo hacia un ServiceName, especificado mediante service.OptionServiceName durante la inicialización del Service.</td>
</tr>
</tbody></table>
**Principios clave de diseño**:
1. Todos los mensajes, RPCs y flujos son transmisiones punto a punto.
- De los microservicios a los bordes, debe especificarse el ID del nodo de borde.
- De los bordes a los microservicios, Frontier enruta según Topic y Method, y finalmente selecciona un microservicio o un MQ externo mediante hashing. Por defecto, el hashing se basa en edgeid, pero puede elegirse random o srcip.
2. Los mensajes requieren un reconocimiento explícito por parte del receptor.
- Para garantizar la semántica de entrega, el receptor debe llamar a msg.Done() o msg.Error(err) para asegurar la consistencia de entrega.
3. Los flujos abiertos por el Multiplexer representan lógicamente una comunicación directa entre microservicios y nodos de borde.
- Una vez que el otro lado recibe el flujo, toda la funcionalidad sobre este flujo llegará directamente al otro lado, omitiendo las políticas de enrutamiento de Frontier.
## Uso
Guía de uso detallada: [docs/USAGE.md](./docs/USAGE.md)
## Configuración
Guía de configuración detallada: [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
## Despliegue
Para una única instancia de Frontier, puede elegir los siguientes métodos para desplegarla según su entorno.
### Docker
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
### Docker-Compose
```bash
git clone https://github.com/singchia/frontier.git
cd dist/compose
docker-compose up -d frontier
```
### Helm
Si está en un entorno Kubernetes, puede usar Helm para desplegar rápidamente una instancia.
```bash
git clone https://github.com/singchia/frontier.git
cd dist/helm
helm install frontier ./ -f values.yaml
```
Su microservicio debería conectarse a ```service/frontier-servicebound-svc:30011```, y su nodo de borde puede conectarse al NodePort donde se expone `:30012`.
### Systemd
Consulte la documentación dedicada a Systemd:
[dist/systemd/README.md](./dist/systemd/README.md)
### Operator
Véase la sección de despliegue en clúster más abajo.
## Clúster
### Arquitectura Frontier + Frontlas
<img src="./docs/diagram/frontlas.png" width="100%">
El componente adicional Frontlas se utiliza para construir el clúster. Frontlas también es un componente sin estado y no almacena otra información en memoria, por lo que requiere una dependencia adicional de Redis. Debe proporcionar información de conexión a Redis a Frontlas, con soporte para `redis`, `sentinel` y `redis-cluster`.
- _Frontier_: componente de comunicación entre los microservicios y el plano de datos de los bordes.
- _Frontlas_: siglas de Frontier Atlas, un componente de gestión del clúster que registra en Redis los metadatos y la información activa de los microservicios y los bordes.
Frontier debe conectarse proactivamente a Frontlas para reportar su estado y el de los microservicios y los bordes. Los puertos por defecto de Frontlas son:
- `:40011` para la conexión de microservicios, reemplazando al puerto 30011 de una única instancia Frontier.
- `:40012` para la conexión de Frontier para reportar estado.
Puede desplegar el número de instancias Frontier que necesite; en el caso de Frontlas, desplegar dos instancias por separado puede garantizar HA (alta disponibilidad), ya que no guarda estado ni presenta problemas de consistencia.
### Configuración
El `frontier.yaml` de **Frontier** necesita añadir la siguiente configuración:
```yaml
frontlas:
enable: true
dial:
network: tcp
addr:
- 127.0.0.1:40012
metrics:
enable: false
interval: 0
daemon:
# ID único dentro del clúster de Frontier
frontier_id: frontier01
```
Frontier debe conectarse a Frontlas para reportar su estado y el de los microservicios y bordes.
Configuración mínima del `frontlas.yaml` de **Frontlas**:
```yaml
control_plane:
listen:
# Los microservicios se conectan a esta dirección para descubrir bordes en el clúster
network: tcp
addr: 0.0.0.0:40011
frontier_plane:
# Frontier se conecta a esta dirección
listen:
network: tcp
addr: 0.0.0.0:40012
expiration:
# Tiempo de expiración de los metadatos de microservicio en Redis
service_meta: 30
# Tiempo de expiración de los metadatos de borde en Redis
edge_meta: 30
redis:
# Soporte para conexiones standalone, sentinel y cluster
mode: standalone
standalone:
network: tcp
addr: redis:6379
db: 0
```
### Uso
Dado que Frontlas se utiliza para descubrir los Frontiers disponibles, los microservicios deben ajustarse de la siguiente manera:
**Obtención de Service desde un microservicio**
```golang
package main
import (
"net"
"github.com/singchia/frontier/api/dataplane/v1/service"
)
func main() {
// Usar NewClusterService para obtener Service
svc, err := service.NewClusterService("127.0.0.1:40011")
// Empezar a usar service; todo lo demás permanece igual
}
```
**Obtención de la dirección de conexión por parte de los nodos de borde**
Los nodos de borde siguen conectándose a Frontier, pero pueden obtener las direcciones de los Frontier disponibles desde Frontlas. Frontlas proporciona una interfaz para listar las instancias de Frontier:
```bash
curl -X GET http://127.0.0.1:40011/cluster/v1/frontiers
```
Puede envolver esta interfaz para proporcionar balanceo de carga o alta disponibilidad a los nodos de borde, o añadir mTLS para entregarla directamente a los nodos de borde (no recomendado).
Para el gRPC del plano de control, véase la [definición Protobuf](./api/controlplane/frontlas/v1/cluster.proto).
El plano de control de Frontlas difiere del de Frontier en que está orientado al clúster, y actualmente solo proporciona interfaces de lectura para el clúster.
```protobuf
service ClusterService {
rpc GetFrontierByEdge(GetFrontierByEdgeIDRequest) returns (GetFrontierByEdgeIDResponse);
rpc ListFrontiers(ListFrontiersRequest) returns (ListFrontiersResponse);
rpc ListEdges(ListEdgesRequest) returns (ListEdgesResponse);
rpc GetEdgeByID(GetEdgeByIDRequest) returns (GetEdgeByIDResponse);
rpc GetEdgesCount(GetEdgesCountRequest) returns (GetEdgesCountResponse);
rpc ListServices(ListServicesRequest) returns (ListServicesResponse);
rpc GetServiceByID(GetServiceByIDRequest) returns (GetServiceByIDResponse);
rpc GetServicesCount(GetServicesCountRequest) returns (GetServicesCountResponse);
}
```
## Kubernetes
### Operator
**Instalar CRD y Operator**
Siga estos pasos para instalar y desplegar el Operator en su entorno .kubeconfig:
```bash
git clone https://github.com/singchia/frontier.git
cd dist/crd
kubectl apply -f install.yaml
```
Comprobar CRD:
```bash
kubectl get crd frontierclusters.frontier.singchia.io
```
Comprobar Operator:
```bash
kubectl get all -n frontier-system
```
**FrontierCluster**
```yaml
apiVersion: frontier.singchia.io/v1alpha1
kind: FrontierCluster
metadata:
labels:
app.kubernetes.io/name: frontiercluster
app.kubernetes.io/managed-by: kustomize
name: frontiercluster
spec:
frontier:
# Frontier de instancia única
replicas: 2
# Puerto del lado de microservicio
servicebound:
port: 30011
# Puerto del lado de nodo de borde
edgebound:
port: 30012
frontlas:
# Frontlas de instancia única
replicas: 1
# Puerto del plano de control
controlplane:
port: 40011
redis:
# Configuración de Redis del que se depende
addrs:
- rfs-redisfailover:26379
password: your-password
masterName: mymaster
redisType: sentinel
```
Guarde como `frontiercluster.yaml` y ejecute
```
kubectl apply -f frontiercluster.yaml
```
En 1 minuto tendrá un clúster con 2 instancias de Frontier + 1 de Frontlas.
Compruebe el estado del despliegue de los recursos con:
```bash
kubectl get all -l app=frontiercluster-frontier
kubectl get all -l app=frontiercluster-frontlas
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontier-57d565c89-dn6n8 1/1 Running 0 7m22s
pod/frontiercluster-frontier-57d565c89-nmwmt 1/1 Running 0 7m22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-edgebound-svc NodePort 10.233.23.174 <none> 30012:30012/TCP 8m7s
service/frontiercluster-servicebound-svc ClusterIP 10.233.29.156 <none> 30011/TCP 8m7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontier 2/2 2 2 7m22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontier-57d565c89 2 2 2 7m22s
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontlas-85c4fb6d9b-5clkh 1/1 Running 0 8m11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-frontlas-svc ClusterIP 10.233.0.23 <none> 40011/TCP,40012/TCP 8m11s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontlas 1/1 1 1 8m11s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontlas-85c4fb6d9b 1 1 1 8m11s
```
Su microservicio debería conectarse a `service/frontiercluster-frontlas-svc:40011`, y su nodo de borde puede conectarse al NodePort donde se expone `:30012`.
## Desarrollo
### Roadmap
Véase [ROADMAP](./ROADMAP.md).
### Contribuciones
Si encuentra algún error, abra una issue y los mantenedores responderán con prontitud.
Si desea enviar funcionalidades o resolver problemas del proyecto con mayor rapidez, los PR son bienvenidos bajo estas sencillas condiciones:
- Mantener un estilo de código coherente
- Cada envío incluye una única funcionalidad
- El código enviado incluye pruebas unitarias
## Pruebas
### Funcionalidad Stream
<img src="./docs/diagram/stream.png" width="100%">
## Comunidad
<p align=center>
<img src="./docs/diagram/wechat.JPG" width="30%">
</p>
Únase a nuestro grupo de WeChat para debates y soporte.
## Licencia
Publicado bajo la [Apache License 2.0](https://github.com/singchia/geminio/blob/main/LICENSE).
---
¡Una estrella ⭐️ sería muy apreciada ♥️!
+464
View File
@@ -0,0 +1,464 @@
<p align=center>
<img src="./docs/diagram/frontier-logo.png" width="30%">
</p>
<div align="center">
[![Go](https://github.com/singchia/frontier/actions/workflows/go.yml/badge.svg)](https://github.com/singchia/frontier/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/singchia/frontier)](https://goreportcard.com/report/github.com/singchia/frontier)
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[English](./README.md) | [简体中文](./README_zh.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Español](./README_es.md) | Français | [Deutsch](./README_de.md)
</div>
Frontier est une passerelle de connexion longue durée open source **full-duplex**, écrite en Go. Elle permet aux microservices d'atteindre directement les nœuds de périphérie ou les clients, et inversement. Elle fournit un **RPC bidirectionnel** full-duplex, de la **messagerie** et des **flux point à point**. Frontier suit les principes d'architecture **cloud-native**, prend en charge un déploiement rapide de clusters via Operator, et est conçue pour la **haute disponibilité** et la **mise à l'échelle élastique** jusqu'à des millions de nœuds de périphérie ou de clients en ligne.
## Table des matières
- [Fonctionnalités](#fonctionnalités)
- [Démarrage rapide](#démarrage-rapide)
- [Architecture](#architecture)
- [Utilisation](#utilisation)
- [Configuration](#configuration)
- [Déploiement](#déploiement)
- [Cluster](#cluster)
- [Kubernetes](#kubernetes)
- [Développement](#développement)
- [Tests](#tests)
- [Communauté](#communauté)
- [Licence](#licence)
## Démarrage rapide
1. Lancer une instance unique de Frontier :
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
2. Compiler et exécuter les exemples :
```bash
make examples
```
Exécuter l'exemple de salon de discussion :
```bash
# Terminal 1
./bin/chatroom_service
# Terminal 2
./bin/chatroom_agent
```
Vidéo de démonstration :
https://github.com/singchia/frontier/assets/15531166/18b01d96-e30b-450f-9610-917d65259c30
## Fonctionnalités
- **RPC bidirectionnel** : les services et les bords peuvent s'appeler mutuellement avec équilibrage de charge.
- **Messagerie** : publication/réception par sujet entre services, bords et MQ externe.
- **Flux point à point** : ouverture de flux directs pour le proxy, le transfert de fichiers et le trafic personnalisé.
- **Déploiement cloud-native** : exécution via Docker, Compose, Helm ou Operator.
- **Haute disponibilité et mise à l'échelle** : prise en charge de la reconnexion, du clustering et de la mise à l'échelle horizontale avec Frontlas.
- **Authentification et présence** : authentification des bords et notifications en ligne/hors ligne.
- **APIs du plan de contrôle** : APIs gRPC et REST pour interroger et gérer les nœuds en ligne.
## Architecture
**Composant Frontier**
<img src="./docs/diagram/frontier.png" width="100%">
- _Service End_ : point d'entrée pour les fonctions des microservices, se connecte par défaut.
- _Edge End_ : point d'entrée pour les fonctions des nœuds de périphérie ou des clients.
- _Publish/Receive_ : publication et réception de messages.
- _Call/Register_ : appel et enregistrement de fonctions.
- _OpenStream/AcceptStream_ : ouverture et acceptation de flux point à point (connexions).
- _External MQ_ : Frontier prend en charge, selon la configuration, le transfert des messages publiés depuis les nœuds de périphérie vers des sujets MQ externes.
Frontier exige que les microservices et les nœuds de périphérie se connectent activement à Frontier. Les métadonnées de Service et Edge (sujets de réception, RPC, noms de service, etc.) peuvent être transportées lors de la connexion. Les ports de connexion par défaut sont :
- :30011 : pour que les microservices se connectent et obtiennent Service.
- :30012 : pour que les nœuds de périphérie se connectent et obtiennent Edge.
- :30010 : pour les opérateurs ou programmes qui utilisent le plan de contrôle.
### Fonctionnalités
<table><thead>
<tr>
<th>Fonction</th>
<th>Initiateur</th>
<th>Destinataire</th>
<th>Méthode</th>
<th>Méthode de routage</th>
<th>Description</th>
</tr></thead>
<tbody>
<tr>
<td rowspan="2">Messager</td>
<td>Service</td>
<td>Edge</td>
<td>Publish</td>
<td>EdgeID+Topic</td>
<td>Doit publier vers un EdgeID spécifique, le sujet par défaut est vide. Le bord appelle Receive pour recevoir le message, puis après traitement, doit appeler msg.Done() ou msg.Error(err) pour garantir la cohérence du message.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service ou External MQ</td>
<td>Publish</td>
<td>Topic</td>
<td>Doit publier vers un sujet ; Frontier sélectionne un Service ou un MQ spécifique selon le sujet.</td>
</tr>
<tr>
<td rowspan="2">RPCer</td>
<td>Service</td>
<td>Edge</td>
<td>Call</td>
<td>EdgeID+Method</td>
<td>Doit appeler un EdgeID spécifique, en transportant le nom de la méthode.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>Call</td>
<td>Method</td>
<td>Doit appeler une méthode ; Frontier sélectionne un Service spécifique selon le nom de la méthode.</td>
</tr>
<tr>
<td rowspan="2">Multiplexer</td>
<td>Service</td>
<td>Edge</td>
<td>OpenStream</td>
<td>EdgeID</td>
<td>Doit ouvrir un flux vers un EdgeID spécifique.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>OpenStream</td>
<td>ServiceName</td>
<td>Doit ouvrir un flux vers un ServiceName, spécifié via service.OptionServiceName lors de l'initialisation du Service.</td>
</tr>
</tbody></table>
**Principes de conception clés** :
1. Tous les messages, RPC et flux sont des transmissions point à point.
- Des microservices vers les bords, l'identifiant du nœud de périphérie doit être spécifié.
- Des bords vers les microservices, Frontier route selon Topic et Method, et sélectionne finalement un microservice ou un MQ externe par hachage. Par défaut, le hachage est basé sur edgeid, mais vous pouvez choisir random ou srcip.
2. Les messages nécessitent un accusé de réception explicite du destinataire.
- Afin de garantir la sémantique de livraison, le destinataire doit appeler msg.Done() ou msg.Error(err) pour garantir la cohérence de la livraison.
3. Les flux ouverts par le Multiplexer représentent logiquement une communication directe entre microservices et nœuds de périphérie.
- Une fois que l'autre partie reçoit le flux, toutes les fonctionnalités de ce flux atteignent directement l'autre partie, en contournant les politiques de routage de Frontier.
## Utilisation
Guide d'utilisation détaillé : [docs/USAGE.md](./docs/USAGE.md)
## Configuration
Guide de configuration détaillé : [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
## Déploiement
Pour une instance Frontier unique, vous pouvez choisir les méthodes suivantes pour déployer votre instance selon votre environnement.
### Docker
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
### Docker-Compose
```bash
git clone https://github.com/singchia/frontier.git
cd dist/compose
docker-compose up -d frontier
```
### Helm
Dans un environnement Kubernetes, vous pouvez utiliser Helm pour déployer rapidement une instance.
```bash
git clone https://github.com/singchia/frontier.git
cd dist/helm
helm install frontier ./ -f values.yaml
```
Votre microservice devrait se connecter à ```service/frontier-servicebound-svc:30011```, et votre nœud de périphérie peut se connecter au NodePort où se trouve `:30012`.
### Systemd
Utilisez la documentation Systemd dédiée :
[dist/systemd/README.md](./dist/systemd/README.md)
### Operator
Voir la section de déploiement en cluster ci-dessous.
## Cluster
### Architecture Frontier + Frontlas
<img src="./docs/diagram/frontlas.png" width="100%">
Le composant supplémentaire Frontlas sert à construire le cluster. Frontlas est également un composant sans état qui ne stocke pas d'autres informations en mémoire, il nécessite donc une dépendance supplémentaire à Redis. Vous devez fournir à Frontlas les informations de connexion Redis, en prenant en charge `redis`, `sentinel` et `redis-cluster`.
- _Frontier_ : composant de communication entre les microservices et le plan de données des bords.
- _Frontlas_ : nommé Frontier Atlas, un composant de gestion de cluster qui enregistre dans Redis les métadonnées et les informations d'activité des microservices et des bords.
Frontier doit se connecter de manière proactive à Frontlas pour signaler son propre état ainsi que l'activité et l'état des microservices et des bords. Les ports par défaut de Frontlas sont :
- `:40011` pour la connexion des microservices, remplaçant le port 30011 d'une instance Frontier unique.
- `:40012` pour la connexion de Frontier afin de rapporter l'état.
Vous pouvez déployer autant d'instances Frontier que nécessaire ; pour Frontlas, déployer deux instances séparément peut garantir la haute disponibilité (HA), car il ne stocke pas d'état et ne présente pas de problèmes de cohérence.
### Configuration
Le `frontier.yaml` de **Frontier** doit ajouter la configuration suivante :
```yaml
frontlas:
enable: true
dial:
network: tcp
addr:
- 127.0.0.1:40012
metrics:
enable: false
interval: 0
daemon:
# Identifiant unique au sein du cluster Frontier
frontier_id: frontier01
```
Frontier doit se connecter à Frontlas pour signaler son propre état ainsi que celui des microservices et des bords.
Configuration minimale du `frontlas.yaml` de **Frontlas** :
```yaml
control_plane:
listen:
# Les microservices se connectent à cette adresse pour découvrir les bords du cluster
network: tcp
addr: 0.0.0.0:40011
frontier_plane:
# Frontier se connecte à cette adresse
listen:
network: tcp
addr: 0.0.0.0:40012
expiration:
# Durée d'expiration des métadonnées des microservices dans Redis
service_meta: 30
# Durée d'expiration des métadonnées des bords dans Redis
edge_meta: 30
redis:
# Prise en charge des connexions standalone, sentinel et cluster
mode: standalone
standalone:
network: tcp
addr: redis:6379
db: 0
```
### Utilisation
Comme Frontlas sert à découvrir les Frontiers disponibles, les microservices doivent s'adapter ainsi :
**Microservice obtenant Service**
```golang
package main
import (
"net"
"github.com/singchia/frontier/api/dataplane/v1/service"
)
func main() {
// Utiliser NewClusterService pour obtenir Service
svc, err := service.NewClusterService("127.0.0.1:40011")
// Commencer à utiliser service, tout le reste reste inchangé
}
```
**Nœud de périphérie obtenant l'adresse de connexion**
Les nœuds de périphérie se connectent toujours à Frontier mais peuvent obtenir les adresses des Frontiers disponibles auprès de Frontlas. Frontlas fournit une interface permettant de lister les instances Frontier :
```bash
curl -X GET http://127.0.0.1:40011/cluster/v1/frontiers
```
Vous pouvez encapsuler cette interface pour fournir l'équilibrage de charge ou la haute disponibilité aux nœuds de périphérie, ou ajouter du mTLS pour l'exposer directement aux nœuds (non recommandé).
Pour le gRPC du plan de contrôle, voir la [définition Protobuf](./api/controlplane/frontlas/v1/cluster.proto).
Le plan de contrôle de Frontlas diffère de celui de Frontier car il est orienté cluster, et ne fournit actuellement que des interfaces de lecture pour le cluster.
```protobuf
service ClusterService {
rpc GetFrontierByEdge(GetFrontierByEdgeIDRequest) returns (GetFrontierByEdgeIDResponse);
rpc ListFrontiers(ListFrontiersRequest) returns (ListFrontiersResponse);
rpc ListEdges(ListEdgesRequest) returns (ListEdgesResponse);
rpc GetEdgeByID(GetEdgeByIDRequest) returns (GetEdgeByIDResponse);
rpc GetEdgesCount(GetEdgesCountRequest) returns (GetEdgesCountResponse);
rpc ListServices(ListServicesRequest) returns (ListServicesResponse);
rpc GetServiceByID(GetServiceByIDRequest) returns (GetServiceByIDResponse);
rpc GetServicesCount(GetServicesCountRequest) returns (GetServicesCountResponse);
}
```
## Kubernetes
### Operator
**Installer CRD et Operator**
Suivez ces étapes pour installer et déployer l'Operator dans votre environnement .kubeconfig :
```bash
git clone https://github.com/singchia/frontier.git
cd dist/crd
kubectl apply -f install.yaml
```
Vérifier la CRD :
```bash
kubectl get crd frontierclusters.frontier.singchia.io
```
Vérifier l'Operator :
```bash
kubectl get all -n frontier-system
```
**FrontierCluster**
```yaml
apiVersion: frontier.singchia.io/v1alpha1
kind: FrontierCluster
metadata:
labels:
app.kubernetes.io/name: frontiercluster
app.kubernetes.io/managed-by: kustomize
name: frontiercluster
spec:
frontier:
# Frontier en instance unique
replicas: 2
# Port côté microservice
servicebound:
port: 30011
# Port côté nœud de périphérie
edgebound:
port: 30012
frontlas:
# Frontlas en instance unique
replicas: 1
# Port du plan de contrôle
controlplane:
port: 40011
redis:
# Configuration Redis dont dépend le système
addrs:
- rfs-redisfailover:26379
password: your-password
masterName: mymaster
redisType: sentinel
```
Enregistrez sous `frontiercluster.yaml`, puis
```
kubectl apply -f frontiercluster.yaml
```
En 1 minute, vous obtiendrez un cluster avec 2 instances Frontier + 1 instance Frontlas.
Vérifiez l'état du déploiement des ressources avec :
```bash
kubectl get all -l app=frontiercluster-frontier
kubectl get all -l app=frontiercluster-frontlas
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontier-57d565c89-dn6n8 1/1 Running 0 7m22s
pod/frontiercluster-frontier-57d565c89-nmwmt 1/1 Running 0 7m22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-edgebound-svc NodePort 10.233.23.174 <none> 30012:30012/TCP 8m7s
service/frontiercluster-servicebound-svc ClusterIP 10.233.29.156 <none> 30011/TCP 8m7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontier 2/2 2 2 7m22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontier-57d565c89 2 2 2 7m22s
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontlas-85c4fb6d9b-5clkh 1/1 Running 0 8m11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-frontlas-svc ClusterIP 10.233.0.23 <none> 40011/TCP,40012/TCP 8m11s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontlas 1/1 1 1 8m11s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontlas-85c4fb6d9b 1 1 1 8m11s
```
Votre microservice devrait se connecter à `service/frontiercluster-frontlas-svc:40011`, et votre nœud de périphérie peut se connecter au NodePort où se trouve `:30012`.
## Développement
### Feuille de route
Voir [ROADMAP](./ROADMAP.md).
### Contributions
Si vous trouvez un bug, ouvrez une issue et les mainteneurs du projet répondront rapidement.
Si vous souhaitez soumettre des fonctionnalités ou résoudre plus rapidement des problèmes du projet, les PR sont les bienvenues sous ces conditions simples :
- Le style de code reste cohérent
- Chaque soumission inclut une seule fonctionnalité
- Le code soumis inclut des tests unitaires
## Tests
### Fonction Stream
<img src="./docs/diagram/stream.png" width="100%">
## Communauté
<p align=center>
<img src="./docs/diagram/wechat.JPG" width="30%">
</p>
Rejoignez notre groupe WeChat pour les discussions et le support.
## Licence
Publié sous la [licence Apache 2.0](https://github.com/singchia/geminio/blob/main/LICENSE).
---
Une étoile ⭐️ serait très appréciée ♥️
+464
View File
@@ -0,0 +1,464 @@
<p align=center>
<img src="./docs/diagram/frontier-logo.png" width="30%">
</p>
<div align="center">
[![Go](https://github.com/singchia/frontier/actions/workflows/go.yml/badge.svg)](https://github.com/singchia/frontier/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/singchia/frontier)](https://goreportcard.com/report/github.com/singchia/frontier)
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[English](./README.md) | [简体中文](./README_zh.md) | 日本語 | [한국어](./README_ko.md) | [Español](./README_es.md) | [Français](./README_fr.md) | [Deutsch](./README_de.md)
</div>
Frontier は Go で書かれた**全二重**のオープンソース長時間接続ゲートウェイです。マイクロサービスからエッジノードやクライアントへの直接通信、およびその逆方向の通信を可能にします。全二重の**双方向 RPC**、**メッセージング**、**ポイントツーポイントストリーム**を提供します。Frontier は**クラウドネイティブ**アーキテクチャの原則に従い、Operator による高速なクラスタデプロイをサポートし、**高可用性**と数百万のオンラインエッジノード/クライアントへの**弾性スケーリング**を実現します。
## 目次
- [特徴](#特徴)
- [クイックスタート](#クイックスタート)
- [アーキテクチャ](#アーキテクチャ)
- [使い方](#使い方)
- [設定](#設定)
- [デプロイ](#デプロイ)
- [クラスタ](#クラスタ)
- [Kubernetes](#kubernetes)
- [開発](#開発)
- [テスト](#テスト)
- [コミュニティ](#コミュニティ)
- [ライセンス](#ライセンス)
## クイックスタート
1. 単一の Frontier インスタンスを起動:
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
2. サンプルをビルドして実行:
```bash
make examples
```
チャットルームのサンプルを実行:
```bash
# ターミナル 1
./bin/chatroom_service
# ターミナル 2
./bin/chatroom_agent
```
デモ動画:
https://github.com/singchia/frontier/assets/15531166/18b01d96-e30b-450f-9610-917d65259c30
## 特徴
- **双方向 RPC**: サービスとエッジが互いに呼び出し可能、ロードバランシング対応。
- **メッセージング**: サービス、エッジ、外部 MQ 間でのトピックベースのパブリッシュ/受信。
- **ポイントツーポイントストリーム**: プロキシ、ファイル転送、カスタムトラフィックのための直接ストリームを開始。
- **クラウドネイティブデプロイ**: Docker、Compose、Helm、Operator での実行が可能。
- **高可用性とスケーリング**: 再接続、クラスタリング、Frontlas による水平スケーリングをサポート。
- **認証とプレゼンス**: エッジ認証およびオンライン/オフライン通知。
- **コントロールプレーン API**: オンラインノードの問い合わせと管理のための gRPC および REST API。
## アーキテクチャ
**Frontier コンポーネント**
<img src="./docs/diagram/frontier.png" width="100%">
- _Service End_: マイクロサービス機能のエントリポイント、デフォルトで接続します。
- _Edge End_: エッジノードまたはクライアント機能のエントリポイント。
- _Publish/Receive_: メッセージのパブリッシュと受信。
- _Call/Register_: 関数の呼び出しと登録。
- _OpenStream/AcceptStream_: ポイントツーポイントストリーム(接続)の開始と受付。
- _External MQ_: Frontier は設定に基づき、エッジノードから発行されたメッセージを外部 MQ トピックへ転送できます。
Frontier はマイクロサービスとエッジノードの双方が能動的に Frontier へ接続する必要があります。接続時に Service と Edge のメタデータ(受信トピック、RPC、サービス名など)を運ぶことができます。デフォルトの接続ポートは以下の通りです:
- :30011: マイクロサービスが Service を取得するために接続するポート。
- :30012: エッジノードが Edge を取得するために接続するポート。
- :30010: 運用担当者またはプログラムがコントロールプレーンを利用するポート。
### 機能
<table><thead>
<tr>
<th>機能</th>
<th>発信側</th>
<th>受信側</th>
<th>メソッド</th>
<th>ルーティング方式</th>
<th>説明</th>
</tr></thead>
<tbody>
<tr>
<td rowspan="2">Messager</td>
<td>Service</td>
<td>Edge</td>
<td>Publish</td>
<td>EdgeID+Topic</td>
<td>特定の EdgeID にパブリッシュする必要があり、デフォルトのトピックは空です。エッジは Receive を呼び出してメッセージを受信し、処理後に msg.Done() または msg.Error(err) を呼び出してメッセージの整合性を確保する必要があります。</td>
</tr>
<tr>
<td>Edge</td>
<td>Service または External MQ</td>
<td>Publish</td>
<td>Topic</td>
<td>トピックにパブリッシュする必要があり、Frontier はトピックに基づいて特定の Service または MQ を選択します。</td>
</tr>
<tr>
<td rowspan="2">RPCer</td>
<td>Service</td>
<td>Edge</td>
<td>Call</td>
<td>EdgeID+Method</td>
<td>特定の EdgeID を呼び出す必要があり、メソッド名を伴います。</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>Call</td>
<td>Method</td>
<td>メソッドを呼び出す必要があり、Frontier はメソッド名に基づいて特定の Service を選択します。</td>
</tr>
<tr>
<td rowspan="2">Multiplexer</td>
<td>Service</td>
<td>Edge</td>
<td>OpenStream</td>
<td>EdgeID</td>
<td>特定の EdgeID に対してストリームを開く必要があります。</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>OpenStream</td>
<td>ServiceName</td>
<td>ServiceName に対してストリームを開く必要があり、Service 初期化時に service.OptionServiceName で指定します。</td>
</tr>
</tbody></table>
**主要な設計原則**:
1. すべてのメッセージ、RPC、ストリームはポイントツーポイント伝送です。
- マイクロサービスからエッジへは、エッジノード ID を指定する必要があります。
- エッジからマイクロサービスへは、Frontier が Topic と Method に基づいてルーティングし、最終的にハッシュにより特定のマイクロサービスまたは外部 MQ を選択します。デフォルトは edgeid に基づくハッシュですが、random や srcip を選択できます。
2. メッセージは受信側による明示的な確認が必要です。
- メッセージ配信のセマンティクスを保証するため、受信側は msg.Done() または msg.Error(err) を呼び出して配信の整合性を確保する必要があります。
3. Multiplexer によって開かれたストリームは、論理的にマイクロサービスとエッジノード間の直接通信を表します。
- 相手側がストリームを受信すると、このストリーム上のすべての機能は Frontier のルーティングポリシーをバイパスして直接相手側に到達します。
## 使い方
詳細な使い方ガイド: [docs/USAGE.md](./docs/USAGE.md)
## 設定
詳細な設定ガイド: [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
## デプロイ
単一の Frontier インスタンスでは、環境に応じて以下の方法から選んでインスタンスをデプロイできます。
### Docker
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
### Docker-Compose
```bash
git clone https://github.com/singchia/frontier.git
cd dist/compose
docker-compose up -d frontier
```
### Helm
Kubernetes 環境の場合、Helm を使用して素早くインスタンスをデプロイできます。
```bash
git clone https://github.com/singchia/frontier.git
cd dist/helm
helm install frontier ./ -f values.yaml
```
マイクロサービスは ```service/frontier-servicebound-svc:30011``` に接続し、エッジノードは `:30012` の NodePort に接続できます。
### Systemd
Systemd 専用ドキュメントを参照:
[dist/systemd/README.md](./dist/systemd/README.md)
### Operator
後述のクラスタデプロイセクションを参照してください。
## クラスタ
### Frontier + Frontlas アーキテクチャ
<img src="./docs/diagram/frontlas.png" width="100%">
追加の Frontlas コンポーネントはクラスタの構築に使用されます。Frontlas もステートレスなコンポーネントで、情報をメモリに保存しないため、Redis への追加依存が必要です。Frontlas に Redis 接続情報を提供する必要があり、`redis`、`sentinel`、`redis-cluster` をサポートします。
- _Frontier_: マイクロサービスとエッジデータプレーン間の通信コンポーネント。
- _Frontlas_: Frontier Atlas の略で、マイクロサービスとエッジのメタデータおよびアクティブ情報を Redis に記録するクラスタ管理コンポーネント。
Frontier は能動的に Frontlas に接続し、自身、マイクロサービス、エッジのアクティブ状態を報告する必要があります。Frontlas のデフォルトポート:
- `:40011` マイクロサービス接続用、単一 Frontier インスタンスの 30011 ポートを置き換えます。
- `:40012` Frontier がステータス報告のために接続するポート。
必要に応じて任意の数の Frontier インスタンスをデプロイでき、Frontlas は状態を保持せず一貫性の問題がないため、2 インスタンスを別々にデプロイすることで HA(高可用性)を保証できます。
### 設定
**Frontier** の `frontier.yaml` に以下の設定を追加する必要があります:
```yaml
frontlas:
enable: true
dial:
network: tcp
addr:
- 127.0.0.1:40012
metrics:
enable: false
interval: 0
daemon:
# Frontier クラスタ内で一意の ID
frontier_id: frontier01
```
Frontier は Frontlas に接続して、自身、マイクロサービス、エッジのアクティブ状態を報告する必要があります。
**Frontlas** の `frontlas.yaml` の最小構成:
```yaml
control_plane:
listen:
# マイクロサービスはこのアドレスに接続してクラスタ内のエッジを発見します
network: tcp
addr: 0.0.0.0:40011
frontier_plane:
# Frontier はこのアドレスに接続します
listen:
network: tcp
addr: 0.0.0.0:40012
expiration:
# Redis 内のマイクロサービスメタデータの有効期限
service_meta: 30
# Redis 内のエッジメタデータの有効期限
edge_meta: 30
redis:
# standalone、sentinel、cluster 接続をサポート
mode: standalone
standalone:
network: tcp
addr: redis:6379
db: 0
```
### 使い方
Frontlas は利用可能な Frontier を発見するために使用されるため、マイクロサービスは次のように調整する必要があります:
**マイクロサービスが Service を取得**
```golang
package main
import (
"net"
"github.com/singchia/frontier/api/dataplane/v1/service"
)
func main() {
// NewClusterService を使用して Service を取得
svc, err := service.NewClusterService("127.0.0.1:40011")
// service の使用開始、その他はすべて変更なし
}
```
**エッジノードの接続アドレス取得**
エッジノードは引き続き Frontier に接続しますが、Frontlas から利用可能な Frontier のアドレスを取得できます。Frontlas は Frontier インスタンスを一覧取得するインターフェースを提供します:
```bash
curl -X GET http://127.0.0.1:40011/cluster/v1/frontiers
```
このインターフェースをラップしてエッジノードにロードバランシングや高可用性を提供したり、mTLS を追加してエッジノードに直接提供したり(非推奨)できます。
コントロールプレーン gRPC は [Protobuf 定義](./api/controlplane/frontlas/v1/cluster.proto) を参照。
Frontlas のコントロールプレーンは Frontier のそれとは異なり、クラスタ指向のコントロールプレーンであり、現在はクラスタの読み取りインターフェースのみを提供します。
```protobuf
service ClusterService {
rpc GetFrontierByEdge(GetFrontierByEdgeIDRequest) returns (GetFrontierByEdgeIDResponse);
rpc ListFrontiers(ListFrontiersRequest) returns (ListFrontiersResponse);
rpc ListEdges(ListEdgesRequest) returns (ListEdgesResponse);
rpc GetEdgeByID(GetEdgeByIDRequest) returns (GetEdgeByIDResponse);
rpc GetEdgesCount(GetEdgesCountRequest) returns (GetEdgesCountResponse);
rpc ListServices(ListServicesRequest) returns (ListServicesResponse);
rpc GetServiceByID(GetServiceByIDRequest) returns (GetServiceByIDResponse);
rpc GetServicesCount(GetServicesCountRequest) returns (GetServicesCountResponse);
}
```
## Kubernetes
### Operator
**CRD と Operator のインストール**
次の手順で Operator を .kubeconfig 環境にインストール/デプロイします:
```bash
git clone https://github.com/singchia/frontier.git
cd dist/crd
kubectl apply -f install.yaml
```
CRD を確認:
```bash
kubectl get crd frontierclusters.frontier.singchia.io
```
Operator を確認:
```bash
kubectl get all -n frontier-system
```
**FrontierCluster**
```yaml
apiVersion: frontier.singchia.io/v1alpha1
kind: FrontierCluster
metadata:
labels:
app.kubernetes.io/name: frontiercluster
app.kubernetes.io/managed-by: kustomize
name: frontiercluster
spec:
frontier:
# 単一インスタンス Frontier
replicas: 2
# マイクロサービス側ポート
servicebound:
port: 30011
# エッジノード側ポート
edgebound:
port: 30012
frontlas:
# 単一インスタンス Frontlas
replicas: 1
# コントロールプレーンポート
controlplane:
port: 40011
redis:
# 依存する Redis の構成
addrs:
- rfs-redisfailover:26379
password: your-password
masterName: mymaster
redisType: sentinel
```
`frontiercluster.yaml` として保存し、
```
kubectl apply -f frontiercluster.yaml
```
1 分以内に、2 インスタンスの Frontier 1 インスタンスの Frontlas クラスタが得られます。
次のコマンドでリソースのデプロイ状況を確認:
```bash
kubectl get all -l app=frontiercluster-frontier
kubectl get all -l app=frontiercluster-frontlas
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontier-57d565c89-dn6n8 1/1 Running 0 7m22s
pod/frontiercluster-frontier-57d565c89-nmwmt 1/1 Running 0 7m22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-edgebound-svc NodePort 10.233.23.174 <none> 30012:30012/TCP 8m7s
service/frontiercluster-servicebound-svc ClusterIP 10.233.29.156 <none> 30011/TCP 8m7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontier 2/2 2 2 7m22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontier-57d565c89 2 2 2 7m22s
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontlas-85c4fb6d9b-5clkh 1/1 Running 0 8m11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-frontlas-svc ClusterIP 10.233.0.23 <none> 40011/TCP,40012/TCP 8m11s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontlas 1/1 1 1 8m11s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontlas-85c4fb6d9b 1 1 1 8m11s
```
マイクロサービスは `service/frontiercluster-frontlas-svc:40011` に接続し、エッジノードは `:30012` の NodePort に接続できます。
## 開発
### ロードマップ
[ROADMAP](./ROADMAP.md) を参照。
### コントリビューション
バグを発見した場合は issue を開いてください。プロジェクトメンテナが速やかに対応します。
機能の提出や、プロジェクト課題のより迅速な解決をご希望の場合は、次のシンプルな条件のもとで PR を提出いただけます:
- コードスタイルの一貫性を保つこと
- 1 回の提出につき 1 つの機能とすること
- 提出するコードにユニットテストを含めること
## テスト
### Stream 機能
<img src="./docs/diagram/stream.png" width="100%">
## コミュニティ
<p align=center>
<img src="./docs/diagram/wechat.JPG" width="30%">
</p>
議論とサポートのために WeChat グループへご参加ください。
## ライセンス
[Apache License 2.0](https://github.com/singchia/geminio/blob/main/LICENSE) の下で公開されています。
---
Star ⭐️ をいただけると大変嬉しいです ♥️
+464
View File
@@ -0,0 +1,464 @@
<p align=center>
<img src="./docs/diagram/frontier-logo.png" width="30%">
</p>
<div align="center">
[![Go](https://github.com/singchia/frontier/actions/workflows/go.yml/badge.svg)](https://github.com/singchia/frontier/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/singchia/frontier)](https://goreportcard.com/report/github.com/singchia/frontier)
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[English](./README.md) | [简体中文](./README_zh.md) | [日本語](./README_ja.md) | 한국어 | [Español](./README_es.md) | [Français](./README_fr.md) | [Deutsch](./README_de.md)
</div>
Frontier는 Go로 작성된 **전이중(full-duplex)** 오픈소스 장기 연결 게이트웨이입니다. 마이크로서비스가 엣지 노드나 클라이언트에 직접 도달할 수 있도록 하고, 그 반대 방향도 지원합니다. 전이중 **양방향 RPC**, **메시징**, **점대점 스트림**을 제공합니다. Frontier는 **클라우드 네이티브** 아키텍처 원칙을 따르며, Operator를 통한 빠른 클러스터 배포를 지원하고, **고가용성**과 수백만 온라인 엣지 노드/클라이언트에 대한 **탄력적 확장**을 위해 설계되었습니다.
## 목차
- [기능](#기능)
- [빠른 시작](#빠른-시작)
- [아키텍처](#아키텍처)
- [사용법](#사용법)
- [구성](#구성)
- [배포](#배포)
- [클러스터](#클러스터)
- [Kubernetes](#kubernetes)
- [개발](#개발)
- [테스트](#테스트)
- [커뮤니티](#커뮤니티)
- [라이선스](#라이선스)
## 빠른 시작
1. 단일 Frontier 인스턴스 실행:
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
2. 예제 빌드 및 실행:
```bash
make examples
```
채팅방 예제 실행:
```bash
# 터미널 1
./bin/chatroom_service
# 터미널 2
./bin/chatroom_agent
```
데모 영상:
https://github.com/singchia/frontier/assets/15531166/18b01d96-e30b-450f-9610-917d65259c30
## 기능
- **양방향 RPC**: 서비스와 엣지가 로드 밸런싱과 함께 서로 호출할 수 있습니다.
- **메시징**: 서비스, 엣지, 외부 MQ 간의 토픽 기반 게시/수신.
- **점대점 스트림**: 프록시, 파일 전송, 사용자 정의 트래픽을 위한 직접 스트림 개설.
- **클라우드 네이티브 배포**: Docker, Compose, Helm 또는 Operator를 통한 실행.
- **고가용성 및 확장**: 재연결, 클러스터링, Frontlas를 통한 수평 확장 지원.
- **인증 및 프레즌스**: 엣지 인증과 온라인/오프라인 알림.
- **컨트롤 플레인 API**: 온라인 노드 조회 및 관리를 위한 gRPC 및 REST API.
## 아키텍처
**Frontier 컴포넌트**
<img src="./docs/diagram/frontier.png" width="100%">
- _Service End_: 마이크로서비스 기능의 진입점, 기본적으로 연결됩니다.
- _Edge End_: 엣지 노드 또는 클라이언트 기능의 진입점.
- _Publish/Receive_: 메시지 게시 및 수신.
- _Call/Register_: 함수 호출 및 등록.
- _OpenStream/AcceptStream_: 점대점 스트림(연결)의 개설 및 수락.
- _External MQ_: Frontier는 구성에 따라 엣지 노드에서 게시된 메시지를 외부 MQ 토픽으로 전달할 수 있습니다.
Frontier는 마이크로서비스와 엣지 노드 모두 능동적으로 Frontier에 연결해야 합니다. 연결 시 Service와 Edge의 메타데이터(수신 토픽, RPC, 서비스 이름 등)를 실어 보낼 수 있습니다. 기본 연결 포트는 다음과 같습니다:
- :30011: 마이크로서비스가 Service를 얻기 위해 연결.
- :30012: 엣지 노드가 Edge를 얻기 위해 연결.
- :30010: 운영자 또는 프로그램이 컨트롤 플레인을 사용하기 위해 연결.
### 기능 상세
<table><thead>
<tr>
<th>기능</th>
<th>개시자</th>
<th>수신자</th>
<th>메서드</th>
<th>라우팅 방식</th>
<th>설명</th>
</tr></thead>
<tbody>
<tr>
<td rowspan="2">Messager</td>
<td>Service</td>
<td>Edge</td>
<td>Publish</td>
<td>EdgeID+Topic</td>
<td>특정 EdgeID로 게시해야 하며, 기본 토픽은 비어 있습니다. 엣지는 Receive를 호출해 메시지를 받고, 처리 후 메시지 일관성을 보장하기 위해 msg.Done() 또는 msg.Error(err)를 반드시 호출해야 합니다.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service 또는 External MQ</td>
<td>Publish</td>
<td>Topic</td>
<td>토픽으로 게시해야 하며, Frontier가 토픽을 기반으로 특정 Service 또는 MQ를 선택합니다.</td>
</tr>
<tr>
<td rowspan="2">RPCer</td>
<td>Service</td>
<td>Edge</td>
<td>Call</td>
<td>EdgeID+Method</td>
<td>특정 EdgeID를 메서드 이름과 함께 호출해야 합니다.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>Call</td>
<td>Method</td>
<td>메서드를 호출해야 하며, Frontier가 메서드 이름을 기반으로 특정 Service를 선택합니다.</td>
</tr>
<tr>
<td rowspan="2">Multiplexer</td>
<td>Service</td>
<td>Edge</td>
<td>OpenStream</td>
<td>EdgeID</td>
<td>특정 EdgeID로 스트림을 개설해야 합니다.</td>
</tr>
<tr>
<td>Edge</td>
<td>Service</td>
<td>OpenStream</td>
<td>ServiceName</td>
<td>ServiceName으로 스트림을 개설해야 하며, Service 초기화 시 service.OptionServiceName으로 지정합니다.</td>
</tr>
</tbody></table>
**주요 설계 원칙**:
1. 모든 메시지, RPC, 스트림은 점대점 전송입니다.
- 마이크로서비스에서 엣지로 보낼 때는 엣지 노드 ID를 지정해야 합니다.
- 엣지에서 마이크로서비스로는 Frontier가 Topic과 Method를 기반으로 라우팅하며, 최종적으로 해싱을 통해 마이크로서비스나 외부 MQ를 선택합니다. 기본값은 edgeid 기반 해싱이지만 random 또는 srcip를 선택할 수 있습니다.
2. 메시지는 수신자의 명시적 확인이 필요합니다.
- 메시지 전달 시맨틱을 보장하기 위해 수신자는 msg.Done() 또는 msg.Error(err)를 호출해 전달 일관성을 보장해야 합니다.
3. Multiplexer가 개설한 스트림은 논리적으로 마이크로서비스와 엣지 노드 간의 직접 통신을 나타냅니다.
- 상대방이 스트림을 받으면, 이 스트림의 모든 기능은 Frontier의 라우팅 정책을 우회하여 상대방에게 직접 도달합니다.
## 사용법
상세 사용 가이드: [docs/USAGE.md](./docs/USAGE.md)
## 구성
상세 구성 가이드: [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)
## 배포
단일 Frontier 인스턴스의 경우, 환경에 따라 다음 방법 중 하나로 Frontier 인스턴스를 배포할 수 있습니다.
### Docker
```bash
docker run -d --name frontier -p 30011:30011 -p 30012:30012 singchia/frontier:1.1.0
```
### Docker-Compose
```bash
git clone https://github.com/singchia/frontier.git
cd dist/compose
docker-compose up -d frontier
```
### Helm
Kubernetes 환경에서는 Helm으로 빠르게 인스턴스를 배포할 수 있습니다.
```bash
git clone https://github.com/singchia/frontier.git
cd dist/helm
helm install frontier ./ -f values.yaml
```
마이크로서비스는 ```service/frontier-servicebound-svc:30011```에 연결해야 하고, 엣지 노드는 `:30012`가 위치한 NodePort에 연결할 수 있습니다.
### Systemd
전용 Systemd 문서 참고:
[dist/systemd/README.md](./dist/systemd/README.md)
### Operator
아래 클러스터 배포 섹션을 참조하세요.
## 클러스터
### Frontier + Frontlas 아키텍처
<img src="./docs/diagram/frontlas.png" width="100%">
추가 Frontlas 컴포넌트는 클러스터 구성에 사용됩니다. Frontlas도 스테이트리스 컴포넌트이며 메모리에 기타 정보를 저장하지 않기 때문에 Redis에 대한 추가 의존성이 필요합니다. Frontlas에 Redis 연결 정보를 제공해야 하며, `redis`, `sentinel`, `redis-cluster`를 지원합니다.
- _Frontier_: 마이크로서비스와 엣지 데이터 플레인 간 통신 컴포넌트.
- _Frontlas_: Frontier Atlas의 약자로, 마이크로서비스와 엣지의 메타데이터 및 활성 정보를 Redis에 기록하는 클러스터 관리 컴포넌트.
Frontier는 Frontlas에 능동적으로 연결하여 자신, 마이크로서비스, 엣지의 활성 상태를 보고해야 합니다. Frontlas의 기본 포트:
- `:40011` 마이크로서비스 연결용, 단일 Frontier 인스턴스의 30011 포트를 대체합니다.
- `:40012` Frontier가 상태 보고를 위해 연결.
필요한 만큼 Frontier 인스턴스를 배포할 수 있으며, Frontlas의 경우 상태를 저장하지 않고 일관성 문제가 없기 때문에 두 인스턴스를 별도로 배포하면 HA(고가용성)를 보장할 수 있습니다.
### 구성
**Frontier**의 `frontier.yaml`에 다음 구성을 추가해야 합니다:
```yaml
frontlas:
enable: true
dial:
network: tcp
addr:
- 127.0.0.1:40012
metrics:
enable: false
interval: 0
daemon:
# Frontier 클러스터 내에서 고유한 ID
frontier_id: frontier01
```
Frontier는 Frontlas에 연결하여 자신, 마이크로서비스, 엣지의 활성 상태를 보고해야 합니다.
**Frontlas**의 `frontlas.yaml` 최소 구성:
```yaml
control_plane:
listen:
# 마이크로서비스는 이 주소에 연결해 클러스터 내 엣지를 발견합니다
network: tcp
addr: 0.0.0.0:40011
frontier_plane:
# Frontier가 이 주소에 연결합니다
listen:
network: tcp
addr: 0.0.0.0:40012
expiration:
# Redis 내 마이크로서비스 메타데이터 만료 시간
service_meta: 30
# Redis 내 엣지 메타데이터 만료 시간
edge_meta: 30
redis:
# standalone, sentinel, cluster 연결 지원
mode: standalone
standalone:
network: tcp
addr: redis:6379
db: 0
```
### 사용법
Frontlas가 사용 가능한 Frontier를 발견하는 데 사용되므로, 마이크로서비스는 다음과 같이 조정해야 합니다:
**마이크로서비스가 Service 획득**
```golang
package main
import (
"net"
"github.com/singchia/frontier/api/dataplane/v1/service"
)
func main() {
// NewClusterService로 Service 획득
svc, err := service.NewClusterService("127.0.0.1:40011")
// service 사용 시작, 나머지는 그대로
}
```
**엣지 노드의 연결 주소 획득**
엣지 노드는 여전히 Frontier에 연결하지만, Frontlas에서 사용 가능한 Frontier 주소를 얻을 수 있습니다. Frontlas는 Frontier 인스턴스를 나열하는 인터페이스를 제공합니다:
```bash
curl -X GET http://127.0.0.1:40011/cluster/v1/frontiers
```
이 인터페이스를 래핑해 엣지 노드에 로드 밸런싱 또는 고가용성을 제공하거나, mTLS를 추가해 엣지 노드에 직접 제공할 수 있습니다(권장하지 않음).
컨트롤 플레인 gRPC는 [Protobuf 정의](./api/controlplane/frontlas/v1/cluster.proto) 참조.
Frontlas 컨트롤 플레인은 Frontier와 다르게 클러스터 지향 컨트롤 플레인이며, 현재는 클러스터에 대한 읽기 인터페이스만 제공합니다.
```protobuf
service ClusterService {
rpc GetFrontierByEdge(GetFrontierByEdgeIDRequest) returns (GetFrontierByEdgeIDResponse);
rpc ListFrontiers(ListFrontiersRequest) returns (ListFrontiersResponse);
rpc ListEdges(ListEdgesRequest) returns (ListEdgesResponse);
rpc GetEdgeByID(GetEdgeByIDRequest) returns (GetEdgeByIDResponse);
rpc GetEdgesCount(GetEdgesCountRequest) returns (GetEdgesCountResponse);
rpc ListServices(ListServicesRequest) returns (ListServicesResponse);
rpc GetServiceByID(GetServiceByIDRequest) returns (GetServiceByIDResponse);
rpc GetServicesCount(GetServicesCountRequest) returns (GetServicesCountResponse);
}
```
## Kubernetes
### Operator
**CRD 및 Operator 설치**
다음 단계에 따라 .kubeconfig 환경에 Operator를 설치 및 배포합니다:
```bash
git clone https://github.com/singchia/frontier.git
cd dist/crd
kubectl apply -f install.yaml
```
CRD 확인:
```bash
kubectl get crd frontierclusters.frontier.singchia.io
```
Operator 확인:
```bash
kubectl get all -n frontier-system
```
**FrontierCluster**
```yaml
apiVersion: frontier.singchia.io/v1alpha1
kind: FrontierCluster
metadata:
labels:
app.kubernetes.io/name: frontiercluster
app.kubernetes.io/managed-by: kustomize
name: frontiercluster
spec:
frontier:
# 단일 인스턴스 Frontier
replicas: 2
# 마이크로서비스 측 포트
servicebound:
port: 30011
# 엣지 노드 측 포트
edgebound:
port: 30012
frontlas:
# 단일 인스턴스 Frontlas
replicas: 1
# 컨트롤 플레인 포트
controlplane:
port: 40011
redis:
# 의존하는 Redis 구성
addrs:
- rfs-redisfailover:26379
password: your-password
masterName: mymaster
redisType: sentinel
```
`frontiercluster.yaml`로 저장하고,
```
kubectl apply -f frontiercluster.yaml
```
1분 이내에 2 인스턴스 Frontier + 1 인스턴스 Frontlas 클러스터가 준비됩니다.
다음 명령으로 리소스 배포 상태를 확인:
```bash
kubectl get all -l app=frontiercluster-frontier
kubectl get all -l app=frontiercluster-frontlas
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontier-57d565c89-dn6n8 1/1 Running 0 7m22s
pod/frontiercluster-frontier-57d565c89-nmwmt 1/1 Running 0 7m22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-edgebound-svc NodePort 10.233.23.174 <none> 30012:30012/TCP 8m7s
service/frontiercluster-servicebound-svc ClusterIP 10.233.29.156 <none> 30011/TCP 8m7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontier 2/2 2 2 7m22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontier-57d565c89 2 2 2 7m22s
```
```
NAME READY STATUS RESTARTS AGE
pod/frontiercluster-frontlas-85c4fb6d9b-5clkh 1/1 Running 0 8m11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontiercluster-frontlas-svc ClusterIP 10.233.0.23 <none> 40011/TCP,40012/TCP 8m11s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/frontiercluster-frontlas 1/1 1 1 8m11s
NAME DESIRED CURRENT READY AGE
replicaset.apps/frontiercluster-frontlas-85c4fb6d9b 1 1 1 8m11s
```
마이크로서비스는 `service/frontiercluster-frontlas-svc:40011`에 연결하고, 엣지 노드는 `:30012`가 위치한 NodePort에 연결할 수 있습니다.
## 개발
### 로드맵
[ROADMAP](./ROADMAP.md) 참조.
### 기여
버그를 발견하면 issue를 열어주세요. 프로젝트 메인테이너가 신속히 응답합니다.
기능을 제출하거나 프로젝트 이슈를 더 빠르게 해결하고 싶다면, 다음의 간단한 조건에 따라 PR을 환영합니다:
- 코드 스타일 일관성 유지
- 각 제출은 하나의 기능 포함
- 제출한 코드에 단위 테스트 포함
## 테스트
### Stream 기능
<img src="./docs/diagram/stream.png" width="100%">
## 커뮤니티
<p align=center>
<img src="./docs/diagram/wechat.JPG" width="30%">
</p>
토론과 지원을 위해 WeChat 그룹에 참여하세요.
## 라이선스
[Apache License 2.0](https://github.com/singchia/geminio/blob/main/LICENSE)에 따라 배포됩니다.
---
Star ⭐️ 부탁드립니다 ♥️
+1 -1
View File
@@ -9,7 +9,7 @@
[![Go Reference](https://pkg.go.dev/badge/badge/github.com/singchia/frontier.svg)](https://pkg.go.dev/github.com/singchia/frontier/api/dataplane/v1/service)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[English](./README.md) | 简体中文
[English](./README.md) | 简体中文 | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Español](./README_es.md) | [Français](./README_fr.md) | [Deutsch](./README_de.md)
</div>
+91 -141
View File
@@ -1,3 +1,15 @@
controlplane:
enable: false
listen:
addr: 0.0.0.0:30010
advertised_addr: ""
network: tcp
tls:
ca_certs: null
certs: null
enable: false
insecure_skip_verify: false
mtls: false
daemon:
frontier_id: ""
pprof:
@@ -7,76 +19,47 @@ daemon:
rlimit:
enable: true
nofile: 102400
controlplane:
enable: false
listen:
network: tcp
addr: 0.0.0.0:30010
# advertised_addr: ""
# tls:
# ca_certs: null
# certs: null
# enable: false
# insecure_skip_verify: false
# mtls: false
servicebound:
listen:
network: tcp
addr: 0.0.0.0:30011
# advertised_addr: ""
# tls:
# ca_certs:
# - ca1.cert
# - ca2.cert
# certs:
# - cert: servicebound.cert
# key: servicebound.key
# enable: false
# insecure_skip_verify: false
# mtls: false
edgebound:
listen:
network: tcp
addr: 0.0.0.0:30012
# advertised_addr: ""
# tls:
# ca_certs:
# - ca1.cert
# - ca2.cert
# certs:
# - cert: edgebound.cert
# key: edgebound.key
# enable: false
# insecure_skip_verify: false
# mtls: false
edgeid_alloc_when_no_idservice_on: true
# bypass:
# addr: 192.168.1.10:8443
# advertised_addr: ""
# network: tcp
# tls:
# ca_certs:
# - ca1.cert
# certs:
# - cert: frontier.cert
# key: frontier.key
# enable: true
# insecure_skip_verify: false
# mtls: true
# bypass_enable: false
dao:
backend: buntdb
debug: false
edgebound:
bypass:
addrs:
- 192.168.1.10:8443
advertised_addr: ""
network: tcp
tls:
ca_certs:
- ca1.cert
certs:
- cert: frontier.cert
key: frontier.key
enable: true
insecure_skip_verify: false
mtls: true
bypass_enable: false
edgeid_alloc_when_no_idservice_on: true
listen:
addr: 0.0.0.0:30012
advertised_addr: ""
network: tcp
tls:
ca_certs:
- ca1.cert
- ca2.cert
certs:
- cert: edgebound.cert
key: edgebound.key
enable: false
insecure_skip_verify: false
mtls: false
exchange:
hashby: edgeid
hashby: ""
frontlas:
enable: false
metrics:
enable: false
interval: 0
dial:
addrs:
- 127.0.0.1:40012
addrs:
- 127.0.0.1:40012
advertised_addr: ""
network: tcp
tls:
ca_certs: null
@@ -84,134 +67,101 @@ frontlas:
enable: false
insecure_skip_verify: false
mtls: false
enable: false
metrics:
enable: false
interval: 0
log:
file:
compress: false
max_age: 30
max_backups: 5
max_size: 100
path: /var/log/frontier/frontier.log
format: text
level: info
output: stdout
mqm:
amqp:
enable: false
addrs: null
# 0 max channels means 2^16 - 1
channel_max: 0
# exchange to declare
enable: false
exchanges: null
# 0 max bytes means unlimited
frame_size: 0
# less than 1s uses the server's interval
heartbeat: 0
# Connection locale that we expect to always be en_US
# Even though servers must return it as per the AMQP 0-9-1 spec,
# we are not aware of it being used other than to satisfy the spec requirements
locale: ""
producer:
# exchange name to produce
exchange: ""
routing_keys: null
# creating application id
app_id: ""
# MIME content encoding
content_encoding: ""
# MIME content type
content_type: ""
# Transient (0 or 1) or Persistent (2)
delivery_mode: 0
exchange: ""
expiration: ""
# message related headers
headers: null
immediate: false
mandatory: false
# 0 to 9
priority: 0
# address to to reply to (ex: RPC)
reply_to: ""
# message type name
routing_keys: null
type: ""
# creating user id - ex: "guest"
user_id: ""
queueBindings: null
queues: null
# Vhost specifies the namespace of permissions, exchanges, queues and
# bindings on the server. Dial sets this to the path parsed from the URL.
vhost: ""
kafka:
enable: false
addrs: null
enable: false
producer:
# topics to notify frontier which topics to allow to publish
topics: null
# The type of compression to use on messages (defaults to no compression).
# Similar to `compression.codec` setting of the JVM producer.
async: false
compression: none
# The level of compression to use on messages. The meaning depends
# on the actual compression type used and defaults to default compression
# level for the codec.
compression_level: 0
# If enabled, the producer will ensure that exactly one copy of each message is
# written.
idempotent: false
# The maximum permitted size of a message (defaults to 1000000). Should be
# set equal to or smaller than the broker's `message.max.bytes`.
max_message_bytes: 0
# The level of acknowledgement reliability needed from the broker (defaults
# to WaitForLocal). Equivalent to the `request.required.acks` setting of the
# JVM producer.
required_acks: 0
# The maximum duration the broker will wait the receipt of the number of
# RequiredAcks (defaults to 10 seconds). This is only relevant when
# RequiredAcks is set to WaitForAll or a number > 1. Only supports
# millisecond resolution, nanoseconds will be truncated. Equivalent to
# the JVM producer's `request.timeout.ms` setting.
timeout: 0
# The following config options control how often messages are batched up and
# sent to the broker. By default, messages are sent as fast as possible, and
# all messages received while the current batch is in-flight are placed
# into the subsequent batch.
flush:
# The best-effort number of bytes needed to trigger a flush. Use the
# global sarama.MaxRequestSize to set a hard upper limit.
bytes: 0
# The best-effort frequency of flushes. Equivalent to
# `queue.buffering.max.ms` setting of JVM producer.
frequency: 0
# The maximum number of messages the producer will send in a single
# broker request. Defaults to 0 for unlimited. Similar to
# `queue.buffering.max.messages` in the JVM producer.
max_messages: 0
# The best-effort number of messages needed to trigger a flush. Use
# `MaxMessages` to set a hard upper limit.
messages: 0
idempotent: false
max_message_bytes: 0
required_acks: 0
retry:
#How long to wait for the cluster to settle between retries
# (default 100ms). Similar to the `retry.backoff.ms` setting of the
# JVM producer.
backoff: 0
# The total number of times to retry sending a message (default 3).
# Similar to the `message.send.max.retries` setting of the JVM producer.
max: 0
timeout: 0
topics: null
nats:
enable: false
addrs: null
producer:
# topics to specific
subjects: null
# jetstream will replace upper producer.
enable: false
jetstream:
enable: false
# jetstream name to publish
name: ""
# jetstream producer
producer:
# topics to specific
subjects: null
producer:
subjects: null
nsq:
enable: false
addrs: null
enable: false
producer:
topics: null
redis:
enable: false
addrs: null
db: 0
enable: false
password: ""
producer:
# topics to specific
channels: null
servicebound:
listen:
addr: 0.0.0.0:30011
advertised_addr: ""
network: tcp
tls:
ca_certs:
- ca1.cert
- ca2.cert
certs:
- cert: servicebound.cert
key: servicebound.key
enable: false
insecure_skip_verify: false
mtls: false
+10
View File
@@ -31,6 +31,16 @@ frontier_manager:
enable: false
insecure_skip_verify: false
mtls: false
log:
file:
compress: false
max_age: 30
max_backups: 5
max_size: 100
path: /var/log/frontier/frontlas.log
format: text
level: info
output: stdout
redis:
client_name: ""
cluster:
+1
View File
@@ -24,6 +24,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8
google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.33.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.5
+2
View File
@@ -271,6 +271,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+205
View File
@@ -0,0 +1,205 @@
package config
import (
"flag"
"fmt"
"io"
"os"
"strings"
"github.com/jumboframes/armorigo/log"
"gopkg.in/natefinch/lumberjack.v2"
"k8s.io/klog/v2"
)
// Log holds unified logging configuration for both frontier and frontlas.
type Log struct {
// Level controls log verbosity for both klog and armorigo.
// Options: "debug", "info", "warn", "error". Default: "info".
Level string `yaml:"level,omitempty" json:"level"`
// Output controls where logs are written.
// Options: "stdout", "stderr", "file", "both" (stdout+file). Default: "stdout".
Output string `yaml:"output,omitempty" json:"output"`
// Format controls the log output format.
// Options: "text", "json". Default: "text".
Format string `yaml:"format,omitempty" json:"format"`
// File holds file-based logging config, used when Output is "file" or "both".
File LogFile `yaml:"file,omitempty" json:"file"`
}
// LogFile configures file-based log output with rotation via lumberjack.
type LogFile struct {
// Path is the log file path. Default: "/var/log/frontier/<component>.log".
Path string `yaml:"path,omitempty" json:"path"`
// MaxSize is the max size in MB before rotation. Default: 100.
MaxSize int `yaml:"max_size,omitempty" json:"max_size"`
// MaxBackups is the max number of old log files to keep. Default: 5.
MaxBackups int `yaml:"max_backups,omitempty" json:"max_backups"`
// MaxAge is the max days to retain old log files. 0 means no age limit. Default: 30.
MaxAge int `yaml:"max_age,omitempty" json:"max_age"`
// Compress enables gzip compression for rotated log files. Default: false.
Compress bool `yaml:"compress,omitempty" json:"compress"`
}
// level mapping: user-facing level -> klog verbosity
var levelToKlogVerbosity = map[string]int{
"debug": 4,
"info": 2,
"warn": 0,
"error": 0,
}
// level mapping: user-facing level -> armorigo level
var levelToArmorigo = map[string]log.Level{
"debug": log.LevelDebug,
"info": log.LevelInfo,
"warn": log.LevelWarn,
"error": log.LevelError,
}
// SetupLogging initializes both klog and armorigo logging based on the unified
// Log config. The component parameter ("frontier" or "frontlas") is used for
// the default log file path.
func SetupLogging(cfg *Log, component string) error {
applyDefaults(cfg, component)
if err := validateConfig(cfg); err != nil {
return err
}
// build the writer(s) for the chosen output mode
writer, err := buildWriter(cfg)
if err != nil {
return err
}
// configure klog
setupKlog(cfg, writer)
// configure armorigo
setupArmorigo(cfg, writer)
return nil
}
func applyDefaults(cfg *Log, component string) {
if cfg.Level == "" {
cfg.Level = "info"
}
cfg.Level = strings.ToLower(cfg.Level)
if cfg.Output == "" {
cfg.Output = "stdout"
}
cfg.Output = strings.ToLower(cfg.Output)
if cfg.Format == "" {
cfg.Format = "text"
}
cfg.Format = strings.ToLower(cfg.Format)
if cfg.File.Path == "" {
cfg.File.Path = "/var/log/frontier/" + component + ".log"
}
if cfg.File.MaxSize <= 0 {
cfg.File.MaxSize = 100
}
if cfg.File.MaxBackups <= 0 {
cfg.File.MaxBackups = 5
}
if cfg.File.MaxAge <= 0 {
cfg.File.MaxAge = 30
}
}
func validateConfig(cfg *Log) error {
if _, ok := levelToKlogVerbosity[cfg.Level]; !ok {
return fmt.Errorf("unsupported log level %q, options: debug, info, warn, error", cfg.Level)
}
switch cfg.Output {
case "stdout", "stderr", "file", "both":
default:
return fmt.Errorf("unsupported log output %q, options: stdout, stderr, file, both", cfg.Output)
}
switch cfg.Format {
case "text", "json":
default:
return fmt.Errorf("unsupported log format %q, options: text, json", cfg.Format)
}
return nil
}
func buildWriter(cfg *Log) (io.Writer, error) {
var fileWriter io.Writer
if cfg.Output == "file" || cfg.Output == "both" {
fileWriter = &lumberjack.Logger{
Filename: cfg.File.Path,
MaxSize: cfg.File.MaxSize,
MaxBackups: cfg.File.MaxBackups,
MaxAge: cfg.File.MaxAge,
Compress: cfg.File.Compress,
}
}
switch cfg.Output {
case "stdout":
return os.Stdout, nil
case "stderr":
return os.Stderr, nil
case "file":
return fileWriter, nil
case "both":
return io.MultiWriter(os.Stdout, fileWriter), nil
}
return os.Stdout, nil
}
func setupKlog(cfg *Log, writer io.Writer) {
// ensure klog flags are initialized
fs := flag.NewFlagSet("klog", flag.ContinueOnError)
klog.InitFlags(fs)
// set verbosity
verbosity := levelToKlogVerbosity[cfg.Level]
fs.Set("v", fmt.Sprintf("%d", verbosity))
// for "error" level, raise the stderr threshold so only warnings+ go through
if cfg.Level == "error" {
fs.Set("stderrthreshold", "WARNING")
}
// direct all klog output to our unified writer
// disable klog's own stderr/file logic — we handle it
fs.Set("logtostderr", "false")
fs.Set("alsologtostderr", "false")
klog.SetOutput(writer)
}
func setupArmorigo(cfg *Log, writer io.Writer) {
log.SetLevel(levelToArmorigo[cfg.Level])
log.SetOutput(writer)
}
// ApplyLogEnvOverrides applies environment variable overrides to the Log config.
// Priority: env > yaml (caller should call this after loading yaml but before SetupLogging).
func ApplyLogEnvOverrides(cfg *Log) {
if v := os.Getenv("LOG_LEVEL"); v != "" {
cfg.Level = v
}
if v := os.Getenv("LOG_OUTPUT"); v != "" {
cfg.Output = v
}
if v := os.Getenv("LOG_FORMAT"); v != "" {
cfg.Format = v
}
if v := os.Getenv("LOG_FILE"); v != "" {
cfg.File.Path = v
}
}
+58 -51
View File
@@ -2,19 +2,15 @@ package config
import (
"encoding/json"
"flag"
"fmt"
"io"
"net"
"os"
"github.com/IBM/sarama"
armio "github.com/jumboframes/armorigo/io"
"github.com/jumboframes/armorigo/log"
"github.com/singchia/frontier/pkg/config"
"github.com/spf13/pflag"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
// daemon related
@@ -252,6 +248,8 @@ type Frontlas struct {
}
type Configuration struct {
Log config.Log `yaml:"log,omitempty" json:"log"`
Daemon Daemon `yaml:"daemon,omitempty" json:"daemon"`
Edgebound Edgebound `yaml:"edgebound" json:"edgebound"`
@@ -273,95 +271,104 @@ type Configuration struct {
func Parse() (*Configuration, error) {
var (
argConfigFile = pflag.String("config", "", "config file, default not configured")
argArmorigoLogLevel = pflag.String("loglevel", "info", "log level for armorigo log")
argLogLevel = pflag.String("loglevel", "", "log level: debug, info, warn, error")
argLogOutput = pflag.String("log-output", "", "log output: stdout, stderr, file, both")
argLogFormat = pflag.String("log-format", "", "log format: text, json")
argLogFile = pflag.String("log-file", "", "log file path, used when output is file or both")
argDaemonRLimitNofile = pflag.Int("daemon-rlimit-nofile", -1, "SetRLimit for number of file of this daemon, default: -1 means ignore")
// TODO more command-line args
config *Configuration
conf *Configuration
)
pflag.Lookup("daemon-rlimit-nofile").NoOptDefVal = "1048576"
// set klog
klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(klogFlags)
// sync the glog and klog flags.
pflag.CommandLine.VisitAll(func(f1 *pflag.Flag) {
f2 := klogFlags.Lookup(f1.Name)
if f2 != nil {
value := f1.Value.String()
if err := f2.Value.Set(value); err != nil {
klog.Fatal(err, "failed to set flag")
return
}
}
})
pflag.CommandLine.AddGoFlagSet(klogFlags)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
// armorigo log
level, err := log.ParseLevel(*argArmorigoLogLevel)
if err != nil {
fmt.Println("parse log level err:", err)
return nil, err
}
log.SetLevel(level)
log.SetOutput(os.Stdout)
// config file
if *argConfigFile != "" {
// TODO the command-line is prior to config file
data, err := os.ReadFile(*argConfigFile)
if err != nil {
return nil, err
}
config = &Configuration{}
if err = yaml.Unmarshal(data, config); err != nil {
conf = &Configuration{}
if err = yaml.Unmarshal(data, conf); err != nil {
return nil, err
}
}
if config == nil {
config = &Configuration{}
if conf == nil {
conf = &Configuration{}
}
// env overrides for log (priority: flag > env > yaml > default)
config.ApplyLogEnvOverrides(&conf.Log)
// flag overrides for log (highest priority)
if *argLogLevel != "" {
conf.Log.Level = *argLogLevel
}
if *argLogOutput != "" {
conf.Log.Output = *argLogOutput
}
if *argLogFormat != "" {
conf.Log.Format = *argLogFormat
}
if *argLogFile != "" {
conf.Log.File.Path = *argLogFile
}
// setup unified logging
if err := config.SetupLogging(&conf.Log, "frontier"); err != nil {
return nil, err
}
// daemon
config.Daemon.RLimit.NumFile = *argDaemonRLimitNofile
if config.Daemon.PProf.CPUProfileRate == 0 {
config.Daemon.PProf.CPUProfileRate = 10000
conf.Daemon.RLimit.NumFile = *argDaemonRLimitNofile
if conf.Daemon.PProf.CPUProfileRate == 0 {
conf.Daemon.PProf.CPUProfileRate = 10000
}
// env
// env overrides for network
sbPort := os.Getenv("FRONTIER_SERVICEBOUND_PORT")
if sbPort != "" {
host, _, err := net.SplitHostPort(config.Servicebound.Listen.Addr)
host, _, err := net.SplitHostPort(conf.Servicebound.Listen.Addr)
if err != nil {
return nil, err
}
config.Servicebound.Listen.Addr = net.JoinHostPort(host, sbPort)
conf.Servicebound.Listen.Addr = net.JoinHostPort(host, sbPort)
}
ebPort := os.Getenv("FRONTIER_EDGEBOUND_PORT")
if ebPort != "" {
host, _, err := net.SplitHostPort(config.Edgebound.Listen.Addr)
host, _, err := net.SplitHostPort(conf.Edgebound.Listen.Addr)
if err != nil {
return nil, err
}
config.Edgebound.Listen.Addr = net.JoinHostPort(host, ebPort)
conf.Edgebound.Listen.Addr = net.JoinHostPort(host, ebPort)
}
nodeName := os.Getenv("NODE_NAME")
if nodeName != "" {
config.Daemon.FrontierID = "frontier-" + nodeName
conf.Daemon.FrontierID = "frontier-" + nodeName
}
frontlasAddr := os.Getenv("FRONTLAS_ADDR")
if frontlasAddr != "" {
config.Frontlas.Enable = true
config.Frontlas.Dial.Addrs = []string{frontlasAddr}
conf.Frontlas.Enable = true
conf.Frontlas.Dial.Addrs = []string{frontlasAddr}
}
return config, nil
return conf, nil
}
func genAllConfig(writer io.Writer) error {
conf := &Configuration{
Log: config.Log{
Level: "info",
Output: "stdout",
Format: "text",
File: config.LogFile{
Path: "/var/log/frontier/frontier.log",
MaxSize: 100,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
},
},
Daemon: Daemon{
RLimit: RLimit{
Enable: true,
+15
View File
@@ -5,6 +5,7 @@ import (
"reflect"
"testing"
"github.com/singchia/frontier/pkg/config"
"gopkg.in/yaml.v2"
)
@@ -17,10 +18,24 @@ func TestParseFlags(t *testing.T) {
{
name: "tryrun",
want: Configuration{
Log: config.Log{
Level: "info",
Output: "stdout",
Format: "text",
File: config.LogFile{
Path: "/var/log/frontier/frontier.log",
MaxSize: 100,
MaxBackups: 5,
MaxAge: 30,
},
},
Daemon: Daemon{
RLimit: RLimit{
NumFile: -1,
},
PProf: PProf{
CPUProfileRate: 10000,
},
},
},
wantErr: false,
+67 -48
View File
@@ -2,7 +2,6 @@ package config
import (
"encoding/json"
"flag"
"io"
"net"
"os"
@@ -13,7 +12,6 @@ import (
"github.com/singchia/frontier/pkg/config"
"github.com/spf13/pflag"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
)
// daemon related
@@ -133,6 +131,8 @@ type FrontierManager struct {
}
type Configuration struct {
Log config.Log `yaml:"log,omitempty" json:"log"`
Daemon Daemon `yaml:"daemon" json:"daemon"`
ControlPlane ControlPlane `yaml:"control_plane" json:"control_plane"`
@@ -145,62 +145,69 @@ type Configuration struct {
func Parse() (*Configuration, error) {
var (
argConfigFile = pflag.String("config", "", "config file, default not configured")
argLogLevel = pflag.String("loglevel", "", "log level: debug, info, warn, error")
argLogOutput = pflag.String("log-output", "", "log output: stdout, stderr, file, both")
argLogFormat = pflag.String("log-format", "", "log format: text, json")
argLogFile = pflag.String("log-file", "", "log file path, used when output is file or both")
argDaemonRLimitNofile = pflag.Int("daemon-rlimit-nofile", -1, "SetRLimit for number of file of this daemon, default: -1 means ignore")
// TODO more command-line args
config *Configuration
conf *Configuration
)
pflag.Lookup("daemon-rlimit-nofile").NoOptDefVal = "1048576"
// set klog
klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(klogFlags)
// sync the glog and klog flags.
pflag.CommandLine.VisitAll(func(f1 *pflag.Flag) {
f2 := klogFlags.Lookup(f1.Name)
if f2 != nil {
value := f1.Value.String()
if err := f2.Value.Set(value); err != nil {
klog.Fatal(err, "failed to set flag")
return
}
}
})
pflag.CommandLine.AddGoFlagSet(klogFlags)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
// config file
if *argConfigFile != "" {
// TODO the command-line is prior to config file
data, err := os.ReadFile(*argConfigFile)
if err != nil {
return nil, err
}
config = &Configuration{}
if err = yaml.Unmarshal(data, config); err != nil {
conf = &Configuration{}
if err = yaml.Unmarshal(data, conf); err != nil {
return nil, err
}
}
if config == nil {
config = &Configuration{}
if conf == nil {
conf = &Configuration{}
}
// env overrides for log (priority: flag > env > yaml > default)
config.ApplyLogEnvOverrides(&conf.Log)
// flag overrides for log (highest priority)
if *argLogLevel != "" {
conf.Log.Level = *argLogLevel
}
if *argLogOutput != "" {
conf.Log.Output = *argLogOutput
}
if *argLogFormat != "" {
conf.Log.Format = *argLogFormat
}
if *argLogFile != "" {
conf.Log.File.Path = *argLogFile
}
// setup unified logging
if err := config.SetupLogging(&conf.Log, "frontlas"); err != nil {
return nil, err
}
// daemon
config.Daemon.RLimit.NumFile = *argDaemonRLimitNofile
if config.Daemon.PProf.CPUProfileRate == 0 {
config.Daemon.PProf.CPUProfileRate = 10000
conf.Daemon.RLimit.NumFile = *argDaemonRLimitNofile
if conf.Daemon.PProf.CPUProfileRate == 0 {
conf.Daemon.PProf.CPUProfileRate = 10000
}
// env, set only exists
// env overrides for network
cpPort := os.Getenv("FRONTLAS_CONTROLPLANE_PORT")
if cpPort != "" {
host, _, err := net.SplitHostPort(config.ControlPlane.Listen.Addr)
host, _, err := net.SplitHostPort(conf.ControlPlane.Listen.Addr)
if err != nil {
return nil, err
}
config.ControlPlane.Listen.Addr = net.JoinHostPort(host, cpPort)
conf.ControlPlane.Listen.Addr = net.JoinHostPort(host, cpPort)
}
redisType := os.Getenv("REDIS_TYPE")
redisAddrs := os.Getenv("REDIS_ADDRS")
@@ -215,30 +222,42 @@ func Parse() (*Configuration, error) {
if err != nil {
return nil, err
}
config.Redis.Standalone.DB = db
config.Redis.Standalone.Addr = addrs[0]
config.Redis.Username = redisUser
config.Redis.Password = redisPassword
config.Redis.Mode = redisType
conf.Redis.Standalone.DB = db
conf.Redis.Standalone.Addr = addrs[0]
conf.Redis.Username = redisUser
conf.Redis.Password = redisPassword
conf.Redis.Mode = redisType
case "sentinel":
addrs := strings.Split(redisAddrs, ",")
config.Redis.Sentinel.Addrs = addrs
config.Redis.Sentinel.MasterName = redisMasterName
config.Redis.Username = redisUser
config.Redis.Password = redisPassword
config.Redis.Mode = redisType
conf.Redis.Sentinel.Addrs = addrs
conf.Redis.Sentinel.MasterName = redisMasterName
conf.Redis.Username = redisUser
conf.Redis.Password = redisPassword
conf.Redis.Mode = redisType
case "cluster":
addrs := strings.Split(redisAddrs, ",")
config.Redis.Cluster.Addrs = addrs
config.Redis.Username = redisUser
config.Redis.Password = redisPassword
config.Redis.Mode = redisType
conf.Redis.Cluster.Addrs = addrs
conf.Redis.Username = redisUser
conf.Redis.Password = redisPassword
conf.Redis.Mode = redisType
}
return config, nil
return conf, nil
}
func genAllConfig(writer io.Writer) error {
conf := &Configuration{
Log: config.Log{
Level: "info",
Output: "stdout",
Format: "text",
File: config.LogFile{
Path: "/var/log/frontier/frontlas.log",
MaxSize: 100,
MaxBackups: 5,
MaxAge: 30,
Compress: false,
},
},
Daemon: Daemon{
RLimit: RLimit{
NumFile: 1024,