Azure Stack Edge Pro에서 파일을 이동하는 C# IoT Edge 모듈 개발

적용 대상:Yes for Pro GPU SKUAzure Stack Edge Pro - GPUYes for Pro 2 SKUAzure Stack Edge Pro 2Yes for Pro R SKUAzure Stack Edge Pro RYes for Mini R SKUAzure Stack Edge Mini R

이 문서에서는 Azure Stack Edge Pro 디바이스를 사용하여 배포할 IoT Edge 모듈을 만드는 방법을 안내합니다. Azure Stack Edge Pro는 데이터를 처리하고 네트워크를 통해 Azure로 보낼 수 있는 스토리지 솔루션입니다.

Azure Stack Edge Pro와 함께 Azure IoT Edge 모듈을 사용하여 Azure로 이동하면서 데이터를 변환할 수 있습니다. 이 문서에 사용된 모듈은 로컬 공유에서 Azure Stack Edge Pro 디바이스의 클라우드 공유로 파일을 복사하는 논리를 구현합니다.

이 문서에서는 다음 방법을 설명합니다.

  • 모듈을 저장하고 관리하는 컨테이너 레지스트리를 만듭니다(Docker 이미지).
  • Azure Stack Edge Pro 디바이스에 배포할 IoT Edge 모듈을 만듭니다.

IoT Edge 모듈 정보

Azure Stack Edge Pro 디바이스는 IoT Edge 모듈을 배포하고 실행할 수 있습니다. 에지 모듈은 기본적으로 디바이스에서 메시지를 수집하거나, 메시지를 변환하거나, IoT Hub에 메시지를 보내는 등의 특정 작업을 수행하는 Docker 컨테이너입니다. 이 문서에서는 로컬 공유에서 Azure Stack Edge Pro 디바이스의 클라우드 공유로 파일을 복사하는 모듈을 만듭니다.

  1. 파일은 Azure Stack Edge Pro 디바이스의 로컬 공유에 기록됩니다.
  2. 파일 이벤트 생성기는 로컬 공유에 기록된 각 파일에 대한 파일 이벤트를 만듭니다. 파일 이벤트는 파일을 수정할 때도 생성됩니다. 그런 다음 파일 이벤트는 IoT Edge 허브(IoT Edge 런타임)로 전송됩니다.
  3. IoT Edge 사용자 지정 모듈은 파일 이벤트를 처리하여 파일의 상대 경로도 포함하는 파일 이벤트 개체를 만듭니다. 모듈은 상대 파일 경로를 사용하여 절대 경로를 생성하고 로컬 공유에서 클라우드 공유로 파일을 복사합니다. 그런 다음, 모듈은 로컬 공유에서 파일을 삭제합니다.

How Azure IoT Edge module works on Azure Stack Edge Pro

파일이 클라우드 공유에 있으면 Azure Storage 계정에 자동으로 업로드됩니다.

필수 조건

시작하기 전에 다음이 있는지 확인합니다.

컨테이너 레지스트리 만들기

Azure 컨테이너 레지스트리는 프라이빗 Docker 컨테이너 이미지를 저장하고 관리할 수 있는 Azure의 프라이빗 Docker 레지스트리입니다. 클라우드에서 사용 가능한 두 개의 인기 있는 Docker 레지스트리 서비스는 Azure Container Registry 및 Docker Hub입니다. 이 문서에서는 Container Registry를 사용합니다.

  1. 브라우저에서 Azure Portal로그인합니다.

  2. 리소스 만들기 > 컨테이너 > Container Registry를 선택합니다. 만들기를 클릭합니다.

  3. 제공:

    1. 5~50의 영숫자 문자를 포함하는 Azure 내의 고유한 레지스트리 이름

    2. 구독을 선택합니다.

    3. 새로 만들거나 기존 리소스 그룹을 선택합니다.

    4. 위치를 선택합니다. 이 위치는 Azure Stack Edge 리소스와 연결된 위치와 동일한 것이 좋습니다.

    5. 관리 사용자를 사용으로 전환합니다.

    6. SKU를 기본으로 설정합니다.

      Create container registry

  4. 만들기를 실행합니다.

  5. 컨테이너 레지스트리를 만든 후에는 해당 레지스트리를 찾고 액세스 키를 선택합니다.

    Get Access keys

  6. 로그인 서버, 사용자 이름암호에 대한 값을 복사합니다. 나중에 이러한 값을 사용하여 Docker 이미지를 레지스트리에 게시하고 레지스트리 자격 증명을 Azure IoT Edge 런타임에 추가합니다.

IoT Edge 모듈 프로젝트 만들기

다음 단계에서는 .NET Core 2.1 SDK를 기반으로 IoT Edge 모듈 프로젝트를 만듭니다. 이 프로젝트는 Visual Studio Code 및 Azure IoT Edge 확장을 사용합니다.

새 솔루션 만들기

사용자 고유의 코드로 사용자 지정할 수 있는 C# 솔루션 템플릿을 만듭니다.

  1. Visual Studio Code에서 보기 > 명령 팔레트를 선택하여 VS Code 명령 팔레트를 엽니다.

  2. 명령 팔레트에서 Azure: 로그인 명령을 입력하고 실행하고 지침에 따라 Azure 계정에 로그인합니다. 이미 로그인한 경우 이 단계를 건너뛸 수 있습니다.

  3. 명령 팔레트에서 Azure IoT Edge: 새로운 IoT Edge 솔루션 명령을 입력하고 실행합니다. 명령 팔레트에서 다음 정보를 제공하여 솔루션을 만듭니다.

    1. 솔루션을 만들 폴더를 선택합니다.

    2. 솔루션의 이름을 제공하거나 기본 EdgeSolution을 적용합니다.

      Create new solution 1

    3. C# 모듈을 모듈 템플릿으로 선택합니다.

    4. 기본 모듈 이름을 할당하려는 이름으로 바꾸고, 이 경우 FileCopyModule입니다.

      Create new solution 2

    5. 이전 섹션에서 만든 컨테이너 레지스트리를 첫 번째 모듈의 이미지 리포지토리로 지정합니다. localhost:5000을 복사한 로그인 서버 값으로 바꿉다.

      최종 문자열은 <Login server name>/<Module name>과 같습니다. 이 예제에서 문자열은 다음과 mycontreg2.azurecr.io/filecopymodule같습니다.

      Create new solution 3

  4. 파일 > 폴더 열기로 이동합니다.

    Create new solution 4

  5. 이전에 만든 EdgeSolution 폴더를 찾아서 가리킵니다. VS Code 창은 최상위 5개의 구성 요소로 IoT Edge 솔루션 작업 영역을 로드합니다. 이 문서에서는 .vscode 폴더, .gitignore 파일, .env 파일 및 deployment.template.json**을 편집하지 않습니다.

    수정하는 유일한 구성 요소는 모듈 폴더입니다. 이 폴더에는 모듈에 대한 C# 코드와 모듈을 컨테이너 이미지로 빌드하는 Docker 파일이 있습니다.

    Create new solution 5

사용자 지정 코드로 모듈 업데이트

  1. VS Code 탐색기에서 모듈 > FileCopyModule > Program.cs를 차례로 엽니다.

  2. FileCopyModule 네임스페스의 맨 위에 나중에 사용되는 형식에 대해 다음 using 문을 추가합니다. Microsoft.Azure.Devices.Client.Transport.Mqtt는 IoT Edge Hub에 메시지를 보내는 프로토콜입니다.

    namespace FileCopyModule
    {
        using Microsoft.Azure.Devices.Client.Transport.Mqtt;
        using Newtonsoft.Json;
    
  3. Program 클래스에 InputFolderPathOutputFolderPath 변수를 추가합니다.

    class Program
        {
            static int counter;
            private const string InputFolderPath = "/home/input";
            private const string OutputFolderPath = "/home/output";
    
  4. 이전 단계 직후 FileEvent 클래스를 추가하여 메시지 본문을 정의합니다.

    /// <summary>
    /// The FileEvent class defines the body of incoming messages. 
    /// </summary>
    private class FileEvent
    {
        public string ChangeType { get; set; }
    
        public string ShareRelativeFilePath { get; set; }
    
        public string ShareName { get; set; }
    }
    
  5. Init 메서드에서 코드는 ModuleClient 개체를 만들고 구성합니다. 이 개체를 사용하면 모듈이 MQTT 프로토콜을 사용하여 로컬 Azure IoT Edge 런타임에 연결하여 메시지를 보내고 받을 수 있습니다. Init 메서드에 사용된 연결 문자열이 IoT Edge 런타임에 의해 모듈에 제공됩니다. 이 코드는 input1 엔드포인트를 통해 IoT Edge 허브에서 메시지를 수신하기 위해 FileCopy 콜백을 등록합니다. Init 메서드다음 코드로 바꿉다.

    /// <summary>
    /// Initializes the ModuleClient and sets up the callback to receive
    /// messages containing file event information
    /// </summary>
    static async Task Init()
    {
        MqttTransportSettings mqttSetting = new MqttTransportSettings(TransportType.Mqtt_Tcp_Only);
        ITransportSettings[] settings = { mqttSetting };
    
        // Open a connection to the IoT Edge runtime
        ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);
        await ioTHubModuleClient.OpenAsync();
        Console.WriteLine("IoT Hub module client initialized.");
    
        // Register callback to be called when a message is received by the module
        await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", FileCopy, ioTHubModuleClient);
    }
    
  6. PipeMessage 메서드에 대한 코드를 제거하고 해당 위치에 FileCopy에 대한 코드를 삽입합니다.

        /// <summary>
        /// This method is called whenever the module is sent a message from the IoT Edge Hub.
        /// This method deserializes the file event, extracts the corresponding relative file path, and creates the absolute input file path using the relative file path and the InputFolderPath.
        /// This method also forms the absolute output file path using the relative file path and the OutputFolderPath. It then copies the input file to output file and deletes the input file after the copy is complete.
        /// </summary>
        static async Task<MessageResponse> FileCopy(Message message, object userContext)
        {
            int counterValue = Interlocked.Increment(ref counter);
    
            try
            {
                byte[] messageBytes = message.GetBytes();
                string messageString = Encoding.UTF8.GetString(messageBytes);
                Console.WriteLine($"Received message: {counterValue}, Body: [{messageString}]");
    
                if (!string.IsNullOrEmpty(messageString))
                {
                    var fileEvent = JsonConvert.DeserializeObject<FileEvent>(messageString);
    
                    string relativeFileName = fileEvent.ShareRelativeFilePath.Replace("\\", "/");
                    string inputFilePath = InputFolderPath + relativeFileName;
                    string outputFilePath = OutputFolderPath + relativeFileName;
    
                    if (File.Exists(inputFilePath))                
                    {
                        Console.WriteLine($"Moving input file: {inputFilePath} to output file: {outputFilePath}");
                        var outputDir = Path.GetDirectoryName(outputFilePath);
                        if (!Directory.Exists(outputDir))
                        {
                            Directory.CreateDirectory(outputDir);
                        }
    
                        File.Copy(inputFilePath, outputFilePath, true);
                        Console.WriteLine($"Copied input file: {inputFilePath} to output file: {outputFilePath}");
                        File.Delete(inputFilePath);
                        Console.WriteLine($"Deleted input file: {inputFilePath}");
                    } 
                    else
                    {
                        Console.WriteLine($"Skipping this event as input file doesn't exist: {inputFilePath}");   
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Caught exception: {0}", ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
    
            Console.WriteLine($"Processed event.");
            return MessageResponse.Completed;
        }
    
  7. 이 파일을 저장합니다.

  8. 이 프로젝트에 대한 기존 코드 샘플을 다운로드할 수도 있습니다. 그런 다음 이 샘플의 program.cs 파일에 대해 저장한 파일의 유효성을 검사할 수 있습니다.

IoT Edge 솔루션 빌드

이전 섹션에서는 IoT Edge 솔루션을 만들고 FileCopyModule에 코드를 추가하여 로컬 공유에서 클라우드 공유로 파일을 복사했습니다. 이제 솔루션을 컨테이너 이미지로 빌드하고 컨테이너 레지스트리에 푸시해야 합니다.

  1. VSCode에서 터미널 > 새 터미널로 이동하고 새로운 Visual Studio Code 통합 터미널을 엽니다.

  2. 통합 터미널에 다음 명령을 입력하여 Docker에 로그인합니다.

    docker login <ACR login server> -u <ACR username>

    컨테이너 레지스트리에서 복사한 로그인 서버 및 사용자 이름을 사용합니다.

    Build and push IoT Edge solution

  3. 암호를 입력하라는 메시지가 표시되면 암호를 입력합니다. Azure Portal의 컨테이너 레지스트리에 있는 액세스 키에서 로그인 서버, 사용자 이름 및 암호 값을 검색할 수도 있습니다.

  4. 자격 증명이 제공되면 모듈 이미지를 Azure 컨테이너 레지스트리로 푸시할 수 있습니다. VS Code 탐색기에서 module.json 파일을 마우스 오른쪽 단추로 클릭하고 빌드 및 푸시 IoT Edge 솔루션을 선택합니다.

    Build and push IoT Edge solution 2

    Visual Studio Code에 솔루션을 빌드하라고 지시하면 통합 터미널에서 Docker 빌드 및 docker push라는 두 가지 명령을 실행합니다. 이 두 명령은 코드를 빌드하고, CSharpModule.dll을 컨테이너화한 다음, 솔루션을 초기화할 때 지정한 컨테이너 레지스트리로 코드를 푸시합니다.

    모듈 플랫폼을 선택하라는 메시지가 표시됩니다. Linux에 해당하는 amd64를 선택합니다.

    Select platform

    Important

    Linux 모듈만 지원됩니다.

    무시할 수 있는 다음 경고가 표시 될 수 있습니다.

    Program.cs(77,44): 경고 CS1998: 이 비동기 메서드는 'await' 연산자가 부족하고 동기적으로 실행됩니다. 'await' 연산자를 사용하여 비블로킹 API 호출을 대기하거나, 'await Task.Run(...)'을 사용하여 백그라운드 스레드에서 CPU 바인딩된 작업을 수행하세요.

  5. VS Code 통합 터미널에서 태그가 있는 전체 컨테이너 이미지 주소를 볼 수 있습니다. 이미지 주소는 module.json 파일에 있는 <repository>:<version>-<platform>형식의 정보로 빌드됩니다. 이 문서의 경우 mycontreg2.azurecr.io/filecopymodule:0.0.1-amd64처럼 보여야 합니다.

다음 단계

Azure Stack Edge Pro에서 이 모듈을 배포하고 실행하려면 모듈 추가 단계를 참조하세요.