Criar conectores Akri no VS Code

Este artigo descreve como criar, validar, depurar e publicar conectores Akri personalizados usando a extensão de prévia do VS Code Azure IoT Operations Akri Connectors.

A extensão dá suporte às seguintes plataformas:

  • Linux
  • Subsistema do Microsoft Windows para Linux (WSL)
  • Windows

A extensão permite que você crie conectores usando as seguintes linguagens de programação:

  • .NET
  • Rust

Pré-requisitos

Ambiente de desenvolvimento:

Configuração do Docker:

  • As imagens usadas pela extensão devem ser puxadas e marcadas localmente antes de você usar a extensão:

    docker pull mcr.microsoft.com/azureiotoperations/devx-runtime:0.1.8
    docker tag mcr.microsoft.com/azureiotoperations/devx-runtime:0.1.8 devx-runtime
    
  • Todos os contêineres que a extensão inicia são configurados para serem executados em uma rede personalizada nomeada aio_akri_network para fins de isolamento de rede:

    docker network create aio_akri_network
    
  • O contêiner DevX usa um volume akri_devx_docker_volume personalizado para armazenar a configuração do cluster:

    docker volume rm akri_devx_docker_volume # delete the volume created from any previous release
    docker volume create akri_devx_docker_volume
    

Para implantar e usar o conector com uma instância de Operações IoT do Azure, você também precisa:

  • Uma instância do Azure IoT Operations.
  • Acesso a um registro de contêiner, como o Registro de Contêiner do Azure, para publicar suas imagens do conector.
  • Um ponto de extremidade de registro de contêiner configurado em sua instância de Operações do Azure IoT para obter suas imagens de conector. Para obter mais informações, consulte Configurar pontos de extremidade do Registro.

Criar e validar um conector Akri

Neste exemplo, você cria um conector HTTP/REST usando a linguagem C#, cria uma imagem do docker e, em seguida, executa o aplicativo conector usando a extensão VS Code:

  1. Pressione Ctrl+Shift+P para abrir a paleta de comandos e pesquisar pelo comando Azure IoT Operations Akri Connectors: Criar um Novo Conector Akri. Crie uma nova pasta chamada my-connectors e selecione-a, selecione C# como o idioma, insira um nome para o conector como rest_connectore selecione PollingTelemetryConnector como o tipo de conector.

  2. A extensão cria um novo workspace nomeado usando o nome do conector escolhido na etapa anterior. O espaço de trabalho inclui a estrutura básica para um conector de telemetria de polling escrito na linguagem C#.

As etapas a seguir pressupõem que você criou um projeto .NET chamado MyConnector.

Importante

O código de exemplo a seguir destina-se apenas a fins ilustrativos e não se destina a ser usado em produção. Em um conector de produção, você deve implementar uma lógica robusta de tratamento de erros e repetição e garantir que todas as credenciais usadas para se conectar ao ativo sejam armazenadas e usadas com segurança. Um conector de qualidade de produção deve implementar o contrato descrito no documento de Contrato do operador e do conector Akri, presente no repositório de SDKs.

Para representar o status do termostato, crie um arquivo chamado ThermostatStatus.cs na MyConnector pasta no workspace com o conteúdo a seguir. Este arquivo modela a resposta JSON do ponto de extremidade REST:

using System.Text.Json.Serialization;

namespace MyConnector
{
    internal class ThermostatStatus
    {
        [JsonPropertyName("desiredTemperature")]
        public double? DesiredTemperature { get; set; }

        [JsonPropertyName("currentTemperature")]
        public double? CurrentTemperature { get; set; }
    }
}    

Para adicionar uma configuração de ponto de dados, crie um arquivo chamado DataPointConfiguration.cs na MyConnector pasta no workspace com o conteúdo a seguir. Esse arquivo representa uma configuração para um ponto de dados especificado pelo usuário na interface de operações:

using System.Text.Json.Serialization;

namespace MyConnector
{
    public class DataPointConfiguration
    {
        [JsonPropertyName("HttpRequestMethod")]
        public string? HttpRequestMethod { get; set; }
    }
} 

Implemente o SampleDatasetAsync método na classe fornecida DatasetSampler . O método usa um Dataset parâmetro como um parâmetro. Um Dataset contém os pontos de dados para o conector processar.

  1. Abra o arquivo MyConnector/DatasetSampler.cs no workspace do VS Code.

  2. Para passar os dados necessários para processar os dados do ponto de extremidade, adicione um construtor na classe DatasetSampler. A classe usa o HttpClient e o EndpointProfileCredentials para se conectar e autenticar no ponto de extremidade do ativo:

    private readonly HttpClient _httpClient;
    private readonly string _assetName;
    private readonly EndpointCredentials? _credentials;
    
    private readonly static JsonSerializerOptions _jsonSerializerOptions = new()
    {
        AllowTrailingCommas = true,
    };
    
    public DatasetSampler(HttpClient httpClient, string assetName, EndpointCredentials? credentials)
    {
        _httpClient = httpClient;
        _assetName = assetName;
        _credentials = credentials;
    }
    
    public ValueTask DisposeAsync()
    {
        _httpClient.Dispose();
        return ValueTask.CompletedTask;
    }
    
  3. Modifique o GetSamplingIntervalAsync método para retornar um intervalo de amostragem de três segundos:

    public Task<TimeSpan> GetSamplingIntervalAsync(AssetDataset dataset, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(TimeSpan.FromSeconds(3));
    }
    

    Observação

    Para simplificar, este exemplo usa um intervalo de amostragem fixo. Em um conector de produção, você pode tornar o intervalo de amostragem configurável usando os metadados do conector para definir uma propriedade de intervalo de amostragem que um usuário pode definir na interface do usuário da experiência de operações.

  4. Substitua o método SampleDatasetAsync existente pela estrutura de tópicos a seguir:

    public async Task<byte[]> SampleDatasetAsync(AssetDataset dataset, CancellationToken cancellationToken = default)
    {
        int retryCount = 0;
        while (true)
        {
            try
            {
                // TODO: Implement your dataset sampling logic here.
            }
            catch (Exception ex)
            {
                if (++retryCount >= 3)
                {
                    throw new InvalidOperationException($"Failed to sample dataset with name {dataset.Name} in asset with name {_assetName}. Error: {ex.Message}", ex);
                }
                await Task.Delay(1000, cancellationToken);
            }
        }
    }
    
  5. No bloco try no método SampleDatasetAsync, adicione o código a seguir para recuperar cada DataPoint do DataSet e extrair os caminhos das fontes de dados. Esses caminhos fazem parte das URLs usadas para buscar os dados do endpoint REST. Os currentTemperature pontos de dados e desiredTemperature foram modelados anteriormente na ThermostatStatus classe. O método de solicitação HTTP é extraído da configuração do ponto de dados modelada na DataPointConfiguration classe:

    AssetDatasetDataPointSchemaElement httpServerDesiredTemperatureDataPoint = dataset.DataPoints!.Where(x => x.Name!.Equals("desiredTemperature"))!.First();
    HttpMethod httpServerDesiredTemperatureHttpMethod = HttpMethod.Parse(JsonSerializer.Deserialize<DataPointConfiguration>(httpServerDesiredTemperatureDataPoint.DataPointConfiguration!, _jsonSerializerOptions)!.HttpRequestMethod);
    string httpServerDesiredTemperatureRequestPath = httpServerDesiredTemperatureDataPoint.DataSource!;
    
    AssetDatasetDataPointSchemaElement httpServerCurrentTemperatureDataPoint = dataset.DataPoints!.Where(x => x.Name!.Equals("currentTemperature"))!.First();
    HttpMethod httpServerCurrentTemperatureHttpMethod = HttpMethod.Parse(JsonSerializer.Deserialize<DataPointConfiguration>(httpServerCurrentTemperatureDataPoint.DataPointConfiguration!, _jsonSerializerOptions)!.HttpRequestMethod);
    string httpServerCurrentTemperatureRequestPath = httpServerCurrentTemperatureDataPoint.DataSource!;
    

    Observação

    Para simplificar, este exemplo mostra apenas como recuperar o método HTTP a ser usado na configuração do ponto de dados. O exemplo não usa esse valor quando faz a solicitação HTTP.

  6. No mesmo método, configure a autenticação usando as credenciais fornecidas se os pontos de extremidade autenticados estiverem em uso:

    if (_credentials != null && _credentials.Username != null && _credentials.Password != null)
    {
        // Note that this sample uses username + password for authenticating the connection to the asset. In general,
        // x509 authentication should be used instead (if available) as it is more secure.
        string httpServerUsername = _credentials.Username;
        string httpServerPassword = _credentials.Password;
        var byteArray = Encoding.ASCII.GetBytes($"{httpServerUsername}:{httpServerPassword}");
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
    }
    

    Esse código extrai as credenciais e as adiciona ao cabeçalho de autorização. O DatasetSampler implementa a autenticação básica com credenciais de nome de usuário e senha.

  7. Em seguida, adicione o código para fazer uma solicitação HTTP ao endpoint, desserialize a resposta e extraia as propriedades CurrentTemperature e DesiredTemperature, colocando-as em um objeto ThermostatStatus.

    var currentTemperatureHttpResponse = await _httpClient.GetAsync(httpServerCurrentTemperatureRequestPath);
    var desiredTemperatureHttpResponse = await _httpClient.GetAsync(httpServerDesiredTemperatureRequestPath);
    
    if (currentTemperatureHttpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized
        || desiredTemperatureHttpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        throw new Exception("Failed to authorize request to HTTP server. Check credentials configured in rest-server-device-definition.yaml.");
    }
    
    currentTemperatureHttpResponse.EnsureSuccessStatusCode();
    desiredTemperatureHttpResponse.EnsureSuccessStatusCode();
    
    ThermostatStatus thermostatStatus = new()
    {
        CurrentTemperature = (JsonSerializer.Deserialize<ThermostatStatus>(await currentTemperatureHttpResponse.Content.ReadAsStreamAsync(), _jsonSerializerOptions)!).CurrentTemperature,
        DesiredTemperature = (JsonSerializer.Deserialize<ThermostatStatus>(await desiredTemperatureHttpResponse.Content.ReadAsStreamAsync(), _jsonSerializerOptions)!).DesiredTemperature
    };
    
  8. Em seguida, serialize o status para JSON e retorne a resposta ao endpoint. Neste exemplo, o conteúdo da resposta HTTP já corresponde ao esquema de mensagem esperado, portanto, nenhuma tradução é necessária:

    // The HTTP response payload matches the expected message schema, so return it as-is
    return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(thermostatStatus));
    

    Dica

    Opcionalmente, o conector pode registrar um esquema no registro de esquema para permitir que outras Operações de IoT do Azure entendam o formato das mensagens.

  9. Por fim, importe os tipos necessários:

    using Azure.Iot.Operations.Connector.Files;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Text.Json;
    

A versão final do código é semelhante ao DatasetSampler.

Implemente o CreateDatasetSampler método na DatasetSamplerProvider classe. Essa classe cria DataSetSampler objetos para injetar no aplicativo conforme necessário.

  1. Abra o MyConnector/DatasetSamplerProvider.cs arquivo no workspace do VS Code.

  2. No método CreateDatasetSampler, retorne um DatasetSampler junto com o endpointCredentials, se o nome do conjunto de dados for thermostat_status:

    if (dataset.Name.Equals("thermostat_status"))
    {
        if (device.Endpoints != null
            && device.Endpoints.Inbound != null
            && device.Endpoints.Inbound.TryGetValue(inboundEndpointName, out var inboundEndpoint))
        {
            var httpClient = new HttpClient()
            {
                BaseAddress = new Uri(inboundEndpoint.Address),
            };
    
            return new DatasetSampler(httpClient, assetName, endpointCredentials);
        }
    }
    
    throw new InvalidOperationException($"Unrecognized dataset with name {dataset.Name} on asset with name {assetName}");
    

    Observação

    Para simplificar, este exemplo pressupõe que o nome do conjunto de dados é sempre thermostat_status. Em um conector de produção, você pode implementar lógica adicional para lidar com vários conjuntos de dados.

A versão final do código é semelhante a DatasetSamplerProvider.

Em seguida, compile o projeto para confirmar que não há erros. Use o comando do VS Code Azure IoT Operations Akri Connectors: Construir um Conector Akri e escolha o modo Release. Este comando mostra o progresso do build no console OUTPUT e notifica você quando o build é concluído. Em seguida, você pode ver uma nova imagem do Docker nomeada <connector_name> com marca release localmente no Docker Desktop.

Para testar o novo conector localmente, siga estas etapas:

  1. Crie um ponto de extremidade local que funcione como um servidor REST para o conector se conectar. explore-iot-operation No repositório clonado anteriormente, execute os seguintes comandos para criar um servidor REST local para teste:

    cd samples/akri-vscode-extension/sample-rest-server
    docker build -t rest-server:latest .
    

    Você pode ver a imagem no Docker Desktop.

  2. Execute o seguinte comando para iniciar o servidor REST em um contêiner local:

    docker run -d --rm --network aio_akri_network --name restserver rest-server:latest
    
  3. Você pode ver o contêiner em execução no Docker Desktop. O servidor REST é acessível em http://restserver:3000 para contêineres em execução em aio_akri_network.

  4. Copie o arquivo rest-server-device-definition.yaml da pasta samples/akri-vscode-extension/rest-server-custom-resources na sua cópia local do repositório explore-iot-operations para a pasta Dispositivos no espaço de trabalho do conector no VS Code. Esse recurso de dispositivo define uma conexão de ponto de extremidade com o servidor REST.

  5. Copie o arquivo rest-server-asset1-definition.yaml da pasta samples/akri-vscode-extension/rest-server-custom-resources na sua cópia local do repositório explore-iot-operations para a pasta Ativos no seu espaço de trabalho do conector no VS Code. Esse ativo publica informações de temperatura do dispositivo para o mqtt/machine/asset1/status tópico MQTT.

  6. Copie o arquivo rest-server-asset2-definition.yaml da pasta samples/akri-vscode-extension/rest-server-custom-resources na sua cópia local do repositório explore-iot-operations para a pasta Ativos no seu espaço de trabalho do conector no VS Code. Esse ativo publica informações de temperatura do dispositivo para o repositório de estado.

  7. Para testar o conector com o dispositivo e os recursos do ativo, acesse o painel Executar e Depurar no espaço de trabalho do VS Code e selecione a configuração Executar um Conector Akri. Essa configuração inicia um terminal que executa as tarefas de pré-lançamento para iniciar o aio-broker contêiner e o conector REST que você desenvolveu em outro contêiner chamado <connector_name>_release. Esse processo leva vários minutos. Você pode ver o fluxo de dados de telemetria do servidor REST para o agente de MQ por meio do conector REST na janela do terminal no VS Code. Os logs de contêiner também são visíveis no Docker Desktop.

  8. Você pode interromper a execução a qualquer momento usando o botão Parar no painel de comandos de depuração. Esse comando limpa e exclui os contêineres aio-broker em execução e <connector_name>_release.

Depurar um conector do Akri

Para depurar um conector Akri baseado em .NET, certifique-se de ter a extensão C# do VS Code instalada. Use o mesmo conector REST criado anteriormente:

  1. Para criar o conector no modo Debug, use o comando VS Code Azure IoT Operations Akri Connectors: Criar um Conector Akri e selecione o modo Debug. Esse comando cria uma imagem local do Docker chamada <connector_name> com a marca debug. Você pode ver a imagem no Docker Desktop.

  2. Você pode adicionar um ponto de interrupção e a execução é interrompida quando o ponto de interrupção é atingido. Tente adicionar um ponto de interrupção no início do SampleDatasetAsync método em DatasetSampler.cs.

  3. Para depurar o conector, vá para o painel Executar e Depurar no espaço de trabalho do VS Code e selecione a configuração Depurar um Conector Akri. Essa configuração inicia um terminal que executa as tarefas de pré-lançamento para iniciar o aio-broker contêiner e o conector REST que você desenvolveu em outro contêiner chamado <connector_name>_debug. Esse processo leva vários minutos. Você pode ver o fluxo de dados de telemetria do servidor REST para o agente de MQ por meio do conector REST na janela do terminal no VS Code. Os logs de contêiner também são visíveis no Docker Desktop.

  4. Use o botão Desconectar no painel de comandos de depuração para encerrar a execução.

Observação

A extensão Akri VS Code inicia o contêiner DevX em um cenário de Execução/Depuração com um período de tempo limite de três minutos. Se o contêiner não concluir a inicialização dentro do período de tempo limite, a extensão encerrará o contêiner.

Aplicar atualizações de configuração

Você pode atualizar dinamicamente as configurações de dispositivos e ativos no ambiente de execução local enquanto seu conector estiver em execução. Essa funcionalidade permite verificar se o conector responde às alterações de configuração. Use os seguintes comandos de extensão do VS Code para fazer essas alterações:

  • Conectores Akri para Operações do Azure IoT: Aplicar YAML do dispositivo no cluster
  • Conectores Akri para Operações do Azure IoT: Aplicar YAML do Ativo no cluster
  • Conectores Akri para Operações do Azure IoT: Excluir YAML do dispositivo do cluster
  • Conectores Akri para Operações de IoT do Azure: excluir o Asset YAML do cluster

Capturar o estado do conector

Para capturar o estado atual do registro de esquema, use o comando de extensão do VS Code Conectores Akri para Operações do Azure IoT: Estado do conector de captura. Esse comando cria uma pasta na pasta OUTPUT no workspace, com um nome baseado no timestamp atual. A pasta criada contém uma cópia do estado atual do registro de esquema, incluindo todos os esquemas criados pelo conector personalizado.

O estado do registro de esquema está sempre visível na Output/ConnectorState pasta. O comando permite capturar o estado do registro de esquema em um ponto específico no tempo.

Publicar uma imagem do conector

Use o comando Conectores Akri para Operações do Azure IoT: Publicar imagem ou metadados do Akri Connector para publicar imagens do conector em um ACR (Registro de Contêiner do Azure) da Microsoft. O comando usa a CLI do Microsoft Azure e os comandos oras. Para publicar em um registro do ACR, você precisa da ID da assinatura do Azure e do nome do registro do ACR.

Configuração de metadados do conector de autor

Utilize o espaço de trabalho do VS Code criado a partir do comando Criar um conector Akri para criar o connector-metadata.json arquivo que esteja em conformidade com o esquema JSON para o esquema de Metadados do Conector 9.0 (versão preliminar) das Operações do Azure IoT. Você pode colocar este arquivo em qualquer lugar no seu espaço de trabalho do conector. A extensão fornece uma funcionalidade de validação estática usando o connector-metadata.json arquivo e mostra avisos no PROBLEMS painel se alguma propriedade necessária estiver ausente.

Publicar artefatos de metadados

Use o comando Conectores Akri para Operações do Azure IoT: Publicar imagem ou metadados do Connector Akri para publicar pastas de metadados em um registro ACR. O comando usa a CLI do Azure e os comandos oras. Para publicar em um registro do ACR, você precisa da ID da assinatura do Azure e do nome do registro do ACR. Atualmente, a extensão espera que os arquivos chamados connector-metadata.json e, opcionalmente additionalConfig.json , estejam presentes em qualquer pasta enviada por push.

Problemas conhecidos

  • As atualizações de configuração resultantes dos comandos do Delete/Apply Asset/Device YAML VS Code atualmente não funcionam no Windows devido a limitações da implementação do CIFS no kernel do Linux. Quaisquer eventos de alteração de arquivo em pastas montadas no host não são propagados para o contêiner pelo Docker para Windows.

  • Quando você exclui um ativo ou dispositivo do cluster usando os comandos do VS Code, o conector do .NET gera atualmente o seguinte erro 404:

    Unhandled exception. Azure.Iot.Operations.Protocol.Retry.RetryExpiredException: Retry expired while attempting the operation. Last known exception is the inner exception.
    ---> Azure.Iot.Operations.Services.AssetAndDeviceRegistry.Models.AkriServiceErrorException: ApiError: assets.namespaces.deviceregistry.microsoft.com "my-rest-thermostat-asset2" not found: NotFound (ErrorResponse { status: "Failure", message: "assets.namespaces.deviceregistry.microsoft.com \"my-rest-thermostat-asset2\" not found", reason: "NotFound", code: 404 })
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.<>c__DisplayClass19_0.<<SetNotificationPreferenceForAssetUpdatesAsync>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.RunWithRetryAsync[TResult](Func`1 taskFunc, CancellationToken cancellationToken)
    --- End of inner exception stack trace ---
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.RunWithRetryAsync[TResult](Func`1 taskFunc, CancellationToken cancellationToken)
    at Azure.Iot.Operations.Services.AssetAndDeviceRegistry.AdrServiceClient.SetNotificationPreferenceForAssetUpdatesAsync(String deviceName, String inboundEndpointName, String assetName, NotificationPreference notificationPreference, Nullable`1 commandTimeout, CancellationToken cancellationToken)
    at Azure.Iot.Operations.Connector.AdrClientWrapper.AssetFileChanged(Object sender, AssetFileChangedEventArgs e)
    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
    at System.Threading.ThreadPoolWorkQueue.Dispatch()
    at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
    
  • Atualmente, iniciar a imagem DevX como um contêiner a partir do WSL sem o Docker Desktop instalado faz com que o contêiner permaneça travado indefinidamente.