Le 18 novembre dernier, Microsoft a publié son émulateur pour Azure Service Bus. Celui-ci complète notamment Azurite (l’émulateur pour Azure Storage), et permet enfin de développer une solution entièrement en local, malgré une dépendance à Azure Service Bus. Outre les aspects financiers, avoir un émulateur permet une meilleur résilience en télétravail: jusqu’à maintenant, une interruption de connexion internet rendait presqu’impossible le développement d’un projet dépendant du Service Bus.
Présentation officielle
La documentation officielle est ici:
Overview of the Azure Service Bus emulator (documentation)
Introducing Local emulator for Azure Service Bus (article d’introduction)
Azure Service Bus Emulator Installer (dépôt GitHub)
L’émulateur est proprosé sous la forme d’une image docker, et dépend de SQL Server. Les divers guides actuellement publiés par Microsoft sont orientés Docker Desktop.
Disposant d’un cluster Kubernetes, j’ai préféré déployer l’émulateur dessus, plutôt que surcharger mon ordinateur portable avec Docker. Cette dernière option m’est également utile en complément, lorsque je suis en déplacement (j’utilise alors un cluster Minikube dans Docker Desktop sur WSL, ce qui me permet de réutiliser à peu près les mêmes manifestes Kubernetes). Mais le reste du temps, je n’utilise pas Docker Desktop.
Déploiement sur Kubernetes
Il y a deux composants à déployer: SQL Server et Service Bus Emulator. J’ai essentiellement traduit les exemples proposés par Microsoft pour Docker, en les adaptant au format des manifestes de Kubernetes.
Curieusement, la documentation de Microsoft pointe vers l’utilisateur de l’image docker mcr.microsoft.com/azure-sql-edge
qui est documenté comme étant en fin de vie (fin prévue pour le 30 September 2025). L’image à utiliser est donc mcr.microsoft.com/mssql/server
(avec le MSSQL_PID
correspondant à l’édition Express).
SQL Server Express
Le groupe de manifestes suivant contient:
- Un secret (pensez à y mettre votre mot de passe pour le compte
sa
de SQL Server). La façon dont vous gérez vos secrets peut varier, c’est pourquoi j’ai été au plus simple dans cet exemple. Personnellement, j’utilise ArgoCD avec ArgoCD Vault Plugin et 1Password Connect. - Un déploiement.
- Un service.
On notera l’absence de persistent volume claim: cette configuration sert à stocker des données temporaires. Les données ne seront pas persistées si le conteneur est supprimé. Pour Azure Service Bus Emulator, ce dernier de toutes façon réinitialise sa base de données à chaque redémarrage, d’après ce que j’ai pu observer dans ses logs. Si vous souhaitez déployer SQL Server de manière plus pérenne, cet exemple ne convient pas. Pour lever tout doute, j’ai nommé le déploiement « sqlserver-inmemory ».
apiVersion: v1
kind: Secret
metadata:
name: sqlserver-secrets
type: Opaque
stringData:
MSSQL_SA_PASSWORD: "..."
---
# See https://mcr.microsoft.com/en-us/artifact/mar/mssql/server/about
apiVersion: apps/v1
kind: Deployment
metadata:
name: sqlserver-inmemory-deployment
labels:
app.kubernetes.io/name: sqlserver-inmemory
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: sqlserver-inmemory
template:
metadata:
labels:
app.kubernetes.io/name: sqlserver-inmemory
spec:
containers:
- name: sqlserver-inmemory
image: "mcr.microsoft.com/mssql/server:2022-CU16-ubuntu-22.04"
resources:
limits:
memory: "2Gi"
cpu: "250m"
ports:
- name: tcp
containerPort: 1433
protocol: TCP
env:
- name: MSSQL_PID
value: "Express"
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: sqlserver-secrets
key: MSSQL_SA_PASSWORD
---
apiVersion: v1
kind: Service
metadata:
name: sqlserver-inmemory-service
labels:
app.kubernetes.io/name: sqlserver-inmemory
spec:
selector:
app.kubernetes.io/name: sqlserver-inmemory
type: ClusterIP
ports:
- name: tcp
port: 1433
protocol: TCP
targetPort: 1433
Une fois ces manifestes déployés, les logs du conteneur devraient indiquer au bout d’un court moment que le serveur est prêt à recevoir des connexion:
[...]
A self-generated certificate was successfully loaded for encryption.
Server is listening on [ 'any' 1433] accept sockets 1.
Server is listening on [ 'any' 1433] accept sockets 1.
Server is listening on [ ::1 1431] accept sockets 1.
Server is listening on [ 127.0.0.1 1431] accept sockets 1.
SQL Server is now ready for client connections. This is an informational message; no user action is required.
Azure Service Bus Emulator
Cette fois, le groupe de manifestes suivant contient:
- Un ConfigMap: vous devrez l’adapter pour vos besoins. C’est ici que les topics, souscriptions, files sont configurées.
- Un déploiement. Celui-ci contient un initContainer qui force l’attente du service SQL Server avant de déployer l’émulateur.
- Un service.
# See https://learn.microsoft.com/en-us/azure/service-bus-messaging/test-locally-with-service-bus-emulator?tabs=docker-linux-container
apiVersion: v1
kind: ConfigMap
metadata:
name: azure-sb-emulator-config
labels:
app.kubernetes.io/name: azure-sb-emulator
data:
config.json: |
{
"UserConfig": {
"Namespaces": [
{
"Name": "sbemulatorns",
"Queues": [ ],
"Topics": [
{
"Name": "my-topic",
"Properties": {
"DefaultMessageTimeToLive": "PT1H",
"DuplicateDetectionHistoryTimeWindow": "PT20S",
"RequiresDuplicateDetection": false
},
"Subscriptions": [
{
"Name": "MyApp1-SubscribeTo-MyTopic1",
"Properties": {
"DeadLetteringOnMessageExpiration": false,
"DefaultMessageTimeToLive": "PT1H",
"LockDuration": "PT1M",
"MaxDeliveryCount": 10,
"ForwardDeadLetteredMessagesTo": "",
"ForwardTo": "",
"RequiresSession": false
},
"Rules": []
}
]
},
]
}
],
"Logging": {
"Type": "Console"
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: azure-sb-emulator-deployment
labels:
app.kubernetes.io/name: azure-sb-emulator
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: azure-sb-emulator
template:
metadata:
labels:
app.kubernetes.io/name: azure-sb-emulator
spec:
initContainers:
- name: init-wait-sqlserver
image: alpine:3
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "for i in $(seq 1 300); do nc -zvw1 sqlserver-inmemory-service 1433 && exit 0 || sleep 3; done; exit 1"]
volumes:
- name: azure-sb-emulator-config
configMap:
name: azure-sb-emulator-config
containers:
- name: azure-sb-emulator
image: "mcr.microsoft.com/azure-messaging/servicebus-emulator:1.0.1"
ports:
- name: sb
containerPort: 5672
protocol: TCP
volumeMounts:
- mountPath: /ServiceBus_Emulator/ConfigFiles/Config.json
name: azure-sb-emulator-config
subPath: config.json
env:
- name: ACCEPT_EULA
value: "Y"
- name: SQL_SERVER
value: sqlserver-inmemory-service
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: sqlserver-secrets
key: MSSQL_SA_PASSWORD
---
apiVersion: v1
kind: Service
metadata:
name: azure-sb-emulator-service
labels:
app.kubernetes.io/name: azure-sb-emulator
spec:
selector:
app.kubernetes.io/name: azure-sb-emulator
type: ClusterIP
ports:
- name: sb
port: 5672
protocol: TCP
targetPort: 5672
Une fois déployé, si tout se passe bien, les logs de l’émulateur montreront que la base SQL Server est initialisée et que l’émulateur est prêt à recevoir des connexions.
On notera quelques défaut de jeunesse, comme le mot de passe du compte sa
de SQL Server écrit en clair plusieurs fois dans les logs:
[...]
info: EmulatorLauncher[0]
Emulator Service is Launching On Platform:CBL-Mariner/Linux,X64
info: EmulatorHealthCheckHelper[0]
Waiting for 15 seconds before starting the health check for SQL
info: SQL-Setup[0]
Dropping databases 'SbMessageContainerDatabase00001' at 'Data Source=sqlserver-inmemory-service,1433;User id=sa;Password=My-Password;Initial Catalog=master;Encrypt=false;'...
info: SQL-Setup[0]
Dropping databases 'SbGatewayDatabase' at 'Data Source=sqlserver-inmemory-service,1433;User id=sa;Password=My-Password;Initial Catalog=master;Encrypt=false;'...
info: SQL-Setup[0]
Creating database 'SbGatewayDatabase' at 'Data Source=sqlserver-inmemory-service,1433;User id=sa;Password=My-Password;Initial Catalog=master;Encrypt=false;'...
info: SQL-Setup[0]
CREATE DATABASE SbGatewayDatabase
info: SQL-Setup[0]
Creating database 'SbMessageContainerDatabase00001' at 'Data Source=sqlserver-inmemory-service,1433;User id=sa;Password=My-Password;Initial Catalog=master;Encrypt=false;'...
info: SQL-Setup[0]
CREATE DATABASE SbMessageContainerDatabase00001
[...]
info: a.F.aFu[0]
Triggering Entity Sync
info: a.F.aFu[0]
Entity Sync complete; Operation Result:True
info: a.F.aFu[0]
User defined entities created for SB Emulator
info: a.F.aFO[0]
Emulator Service is Successfully Up!
[13:55:58 FTL] >Trc Id="30588" Ch="Operational" Lvl="Critical" Kw="4000000000000100" UTC="2024-11-23T13:55:58.289Z" Msg="ContainerId: 1 failed to report load to Winfab runtime. Exception Details: ExceptionId: 2ef0af95-9f0d-427d-a3a6-93c8d7d0c2c7-System.ArgumentException: Service 'Q.Qd' not found.
at P.Ph.A[A]()
at a.A.aAp.aJ()
at a.A.aAp.A(Object)" />
Connexion à l’émulateur
Pour se connecter à l’émulateur depuis notre code, on peut utiliser cette chaîne de connexion:
"Endpoint=sb://127.0.0.1;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"
Microsoft documente localhost
dans ses exemples, mais dans mon cas cela ne fonctionne pas, et il m’a fallu utiliser 127.0.0.1
. J’ai également essayé d’utiliser un port différent de celui par défaut sans succès. C’est pourquoi le service déployé est de type ClusterIP
et pas NodePort
. Pour que cela fonctionne, il faut établir une redirection de port avec kubectl:
kubectl port-forward svc/azure-sb-emulator-service 5672:5672
Mes premiers tests fonctionnent sans problème particulier (je peux publier des messages dans un topic, et les consommer via une souscription).