Linux で初めての Service Fabric コンテナー アプリケーションを作成する

既存のアプリケーションを Service Fabric クラスター上の Linux コンテナー内で実行する場合は、アプリケーションに変更を加える必要はありません。 この記事では、Python の Flask Web アプリケーションが含まれた Docker イメージを作成し、Service Fabric クラスターにデプロイする方法について説明します。 また、Azure Container Registry を使用して、コンテナー化されたアプリケーションを共有する方法についても説明します。 この記事では、Docker の基本的な理解ができていることを前提としています。 Docker の詳細は、「Docker Overview (Docker の概要)」で確認できます。

Note

この記事は Linux 開発環境に適用されます。 Service Fabric クラスター ランタイムと Docker ランタイムが同じ OS で実行されている必要があります。 Windows クラスターで Linux コンテナーを実行することはできません。

前提条件

Docker コンテナーを定義する

Docker Hub にある Python イメージを基にしてイメージをビルドします。

Dockerfile で Docker コンテナーを指定します。 Dockerfile は、コンテナー内で環境をセットアップし、実行するアプリを読み込み、ポートを割り当てるための手順で構成されています。 Dockerfile はイメージを作成する docker build コマンドへの入力です。

空のディレクトリを作成し、Dockerfile というファイル (ファイル拡張子なし) を作成します。 Dockerfile に次のコードを追加して変更を保存します。

# Use an official Python runtime as a base image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt

# Make port 80 available outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

詳細については、Dockerfile のリファレンスを参照してください。

基本的な Web アプリケーションの作成

ポート 80 でリッスンして "Hello World!" を返す Flask Web アプリケーションを作成します。 同じディレクトリに requirements.txt というファイルを作成します。 次のコードを追加して変更を保存します。

Flask

さらに、app.py ファイルを作成して次のスニペットを追加します。

from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():

    return 'Hello World!'


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

Docker にログインしてイメージをビルドする

次に、Web アプリケーションを実行するイメージを作成します。 Docker からパブリック イメージをプルする場合は (Dockerfile の python:2.7-slim など)、匿名のプル要求を行うのではなく、Docker Hub アカウントで認証することをお勧めします。

Note

匿名のプル要求を頻繁に行うと、ERROR: toomanyrequests: Too Many Requests.You have reached your pull rate limit. のような Docker エラーが発生することがあります。このようなエラーを防ぐには、Docker Hub に対して認証を行います。 詳細については、「Azure Container Registry を使用してパブリック コンテンツを管理する」を参照してください。

PowerShell ウィンドウを開き、Dockerfile が格納されているディレクトリに移動します。 次のコマンドを実行します。

docker login
docker build -t helloworldapp .

このコマンドでは、Dockerfile の手順を使用して新しいイメージを構築し、helloworldapp と名前を付けます (-t というタグを付けます)。 コンテナー イメージを構築するには、まず、アプリケーションの追加先となる Docker Hub から基本イメージをダウンロードします。

ビルド コマンドが完了したら、docker images コマンドを実行して、新しいイメージの情報を確認します。

$ docker images
    
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
helloworldapp                 latest              86838648aab6        2 minutes ago       194 MB

ローカルでアプリケーションを実行する

コンテナー レジストリにプッシュする前に、コンテナー化されたアプリケーションがローカルで実行されていることを確認します。

アプリケーションを実行し、コンピューターのポート 4000 をコンテナーの公開されたポート 80 にマッピングします。

docker run -d -p 4000:80 --name my-web-site helloworldapp

name で、実行中のコンテナーに名前を付けます (コンテナー ID ではありません)。

実行中のコンテナーに接続します。 Web ブラウザーで、ポート 4000 で返された IP アドレスを開きます (たとえば、" http://localhost:4000" ")。 "Hello World!" という見出しがブラウザーに表示されます。

Hello World!

コンテナーを停止するには、次を実行します。

docker stop my-web-site

コンテナーを開発コンピューターから削除します。

docker rm my-web-site

コンテナー レジストリにイメージをプッシュする

アプリケーションが Docker で実行されることを確認したら、イメージを Azure Container Registry のレジストリにプッシュします。

docker login を実行し、ご自分のレジストリの資格情報を使用してお使いのコンテナー レジストリにサインインします。

次の例では、Microsoft Entra サービス プリンシパルの ID とパスワードを渡します。 たとえば、自動化シナリオのために、レジストリにサービス プリンシパルを割り当てることができます。 または、ご自分のレジストリのユーザー名とパスワードを使ってサインインすることもできます。

docker login myregistry.azurecr.io -u xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -p myPassword

次のコマンドでは、レジストリへの完全修飾パスを使用して、イメージのタグまたはエイリアスを作成します。 この例では、レジストリのルートが煩雑にならないように、イメージを samples 名前空間に配置しています。

docker tag helloworldapp myregistry.azurecr.io/samples/helloworldapp

コンテナー レジストリにイメージをプッシュします。

docker push myregistry.azurecr.io/samples/helloworldapp

Yeoman で Docker イメージをパッケージ化する

Linux 用の Service Fabric SDK には、Yeoman ジェネレーターが含まれています。これを使用すると、アプリケーションを作成したり、コンテナー イメージを追加したりする作業が簡単になります。 Yeoman を使用し、Docker コンテナーを 1 つだけ含むアプリケーション SimpleContainerApp を作成してみましょう。

Service Fabric コンテナー アプリケーションを作成するには、ターミナル ウィンドウを開き、yo azuresfcontainer を実行します。

アプリケーションとアプリケーション サービスに、それぞれ mycontainermyservice などの名前を付けます。

イメージ名については、コンテナー レジストリにあるコンテナー イメージの URL を指定します (例: "myregistry.azurecr.io/samples/helloworldapp")。

このイメージには定義済みのワークロード エントリ ポイントがあるため、入力コマンド (コンテナーの起動後にコンテナーの実行を維持するためにコンテナー内で実行されるコマンド) を明示的に指定する必要はありません。

インスタンス数を "1" に指定します。

適切な形式でポート マッピングを指定します。 この記事では、80:4000 をポート マッピングとして提供する必要があります。 そうすることで、ホスト コンピューター上のポート 4000 に到着した受信要求がコンテナー上のポート 80 にリダイレクトされるように構成しています。

コンテナー用 Service Fabric Yeoman ジェネレーター

コンテナー リポジトリの認証を構成する

コンテナー イメージのダウンロード用にさまざまな種類の認証を構成する方法については、コンテナー リポジトリの認証に関する記事を参照してください。

分離モードの構成

6.3 のランタイム リリースでは、Linux コンテナーで VM 分離がサポートされています。つまり、process と Hyper-V の 2 つの分離モードがサポートされています。 Hyper-V 分離モードでは、各コンテナーとコンテナー ホスト間でカーネルが分離されます。 Hyper-V 分離は、Clear Containers を使用して実装されています。 分離モードは、Linux クラスター用にアプリケーション マニフェスト ファイルの ServicePackageContainerPolicy 要素に指定されます。 指定できる分離モードは、processhyperv、および default です。 既定は、プロセス分離モードです。 以下のスニペットは、アプリケーション マニフェスト ファイルで分離モードがどのように指定されるかを示しています。

<ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="MyServicePkg" ServiceManifestVersion="1.0.0"/>
      <Policies>
        <ServicePackageContainerPolicy Hostname="votefront" Isolation="hyperv">
          <PortBinding ContainerPort="80" EndpointRef="myServiceTypeEndpoint"/>
        </ServicePackageContainerPolicy>
    </Policies>
  </ServiceManifestImport>

リソース管理を構成する

リソース管理は、コンテナーがホスト上で使用できるリソースを制限します。 ResourceGovernancePolicy 要素はアプリケーション マニフェストで指定され、サービス コード パッケージのリソース制限を宣言するために使用されます。 リソースの制限は、Memory、MemorySwap、CpuShares (CPU の相対的な重み)、MemoryReservationInMB、BlkioWeight (BlockIO の相対的な重み) の各リソースに対して設定できます。 この例では、Guest1Pkg というサービス パッケージが配置されたクラスター ノード上で 1 つのコアを取得しています。 Memory の制限は絶対的であるため、コード パッケージのメモリは両方とも 1,024 MB に制限されます (ソフト保証予約は同じです)。 コード パッケージ (コンテナーまたはプロセス) は、この制限を超えてメモリを割り当てることはできず、割り当てようとするとメモリ不足の例外が発生します。 リソース制限の強制を機能させるには、サービス パッケージ内のすべてのコード パッケージでメモリ制限を指定する必要があります。

<ServiceManifestImport>
  <ServiceManifestRef ServiceManifestName="MyServicePKg" ServiceManifestVersion="1.0.0" />
  <Policies>
    <ServicePackageResourceGovernancePolicy CpuCores="1"/>
    <ResourceGovernancePolicy CodePackageRef="Code" MemoryInMB="1024"  />
  </Policies>
</ServiceManifestImport>

Docker HEALTHCHECK を構成する

Service Fabric では、バージョン 6.1 以降、Docker HEALTHCHECK イベントがシステム正常性レポートに自動的に統合されます。 つまり、コンテナーの HEALTHCHECK が有効な場合、Service Fabric は Docker によって報告されたとおりにコンテナーの正常性状態が変化するたびに正常性を報告します。 OK 正常性レポートは、health_status が "正常" のときに、Service Fabric Explorer に表示され、警告は、health_status が "異常" のときに表示されます。

v6.4 の最新の更新リリース以降、Docker の HEALTHCHECK 評価をエラーとしてレポートするかどうかの選択肢ができました。 このオプションを有効にすると、health_status正常の場合、OK 正常性レポートが表示され、health_status異常の場合、ERROR が表示されます。

コンテナーの正常性の監視のために実行される実際のチェックを指す HEALTHCHECK 命令は、コンテナー イメージを生成するときに使用される Dockerfile に存在する必要があります。

スクリーンショットからは、デプロイされたサービス パッケージ NodeServicePackage の詳細を確認できます。

HealthCheckUnhealthyApp

HealthCheckUnhealthyDsp

ApplicationManifest の ContainerHostPolicies の一部として HealthConfig オプションを指定することによって、コンテナーごとに HEALTHCHECK の動作を構成できます。

<ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="ContainerServicePkg" ServiceManifestVersion="2.0.0" />
    <Policies>
      <ContainerHostPolicies CodePackageRef="Code">
        <HealthConfig IncludeDockerHealthStatusInSystemHealthReport="true"
		      RestartContainerOnUnhealthyDockerHealthStatus="false" 
		      TreatContainerUnhealthyStatusAsError="false" />
      </ContainerHostPolicies>
    </Policies>
</ServiceManifestImport>

既定で、IncludeDockerHealthStatusInSystemHealthReporttrue に設定され、RestartContainerOnUnhealthyDockerHealthStatusfalse に設定され、TreatContainerUnhealthyStatusAsErrorfalse に設定されています。

RestartContainerOnUnhealthyDockerHealthStatustrue に設定すると、異常を繰り返し報告するコンテナーが (おそらく他のノードで) 再起動されます。

TreatContainerUnhealthyStatusAsErrortrue に設定されている場合、コンテナーの health_status異常のとき、ERROR 正常性レポートが表示されます。

Service Fabric クラスター全体で HEALTHCHECK 統合を無効化する場合、EnableDockerHealthCheckIntegrationfalse に設定する必要があります。

アプリケーションをデプロイする

アプリケーションがビルドされたら、Service Fabric CLI を使用してローカル クラスターにデプロイできます。

ローカルの Service Fabric クラスターに接続します。

sfctl cluster select --endpoint http://localhost:19080

https://github.com/Azure-Samples/service-fabric-containers/ にあるテンプレートに用意されているインストール スクリプトを使用してクラスターのイメージ ストアにアプリケーション パッケージをコピーし、アプリケーションの種類を登録して、アプリケーションのインスタンスを作成します。

./install.sh

ブラウザーを開き、http://localhost:19080/Explorer の Service Fabric Explorer に移動します (Mac OS X で Vagrant を使用している場合は、localhost を VM のプライベート IP に置き換えます)。 Applications ノードを展開し、アプリケーションの種類のエントリと、その種類の最初のインスタンスのエントリができたことを確認します。

実行中のコンテナーに接続します。 Web ブラウザーで、ポート 4000 で返された IP アドレスを開きます (たとえば、" http://localhost:4000" ")。 "Hello World!" という見出しがブラウザーに表示されます。

Hello World!

クリーンアップ

ローカル開発クラスターからアプリケーション インスタンスを削除し、その種類のアプリケーションの登録を解除するには、テンプレートに指定されているアンインストール スクリプトを使用します。

./uninstall.sh

コンテナー レジストリにイメージをプッシュした後は、開発コンピューターからローカルのイメージを削除できます。

docker rmi helloworldapp
docker rmi myregistry.azurecr.io/samples/helloworldapp

Service Fabric のアプリケーション マニフェストとサービス マニフェストの完全な例

この記事で使用される完全なサービス マニフェストとアプリケーション マニフェストは次のとおりです。

ServiceManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest Name="myservicePkg"
                 Version="1.0.0"
                 xmlns="http://schemas.microsoft.com/2011/01/fabric"
                 xmlns:xsd="https://www.w3.org/2001/XMLSchema"
                 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
  <ServiceTypes>
    <!-- This is the name of your ServiceType.
         The UseImplicitHost attribute indicates this is a guest service. -->
    <StatelessServiceType ServiceTypeName="myserviceType" UseImplicitHost="true" />
  </ServiceTypes>

  <!-- Code package is your service executable. -->
  <CodePackage Name="Code" Version="1.0.0">
    <EntryPoint>
      <!-- Follow this link for more information about deploying containers 
      to Service Fabric: https://aka.ms/sfguestcontainers -->
      <ContainerHost>
        <ImageName>myregistry.azurecr.io/samples/helloworldapp</ImageName>
        <!-- Pass comma delimited commands to your container: dotnet, myproc.dll, 5" -->
        <!--Commands> dotnet, myproc.dll, 5 </Commands-->
        <Commands></Commands>
      </ContainerHost>
    </EntryPoint>
    <!-- Pass environment variables to your container: -->
    
    <EnvironmentVariables>
      <!--
      <EnvironmentVariable Name="VariableName" Value="VariableValue"/>
      -->
    </EnvironmentVariables>
    
  </CodePackage>

  <Resources>
    <Endpoints>
      <!-- This endpoint is used by the communication listener to obtain the port on which to 
           listen. Please note that if your service is partitioned, this port is shared with 
           replicas of different partitions that are placed in your code. -->
      <Endpoint Name="myServiceTypeEndpoint" UriScheme="http" Port="4000" Protocol="http"/>
    </Endpoints>
  </Resources>
</ServiceManifest>

ApplicationManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest ApplicationTypeName="mycontainerType"
                     ApplicationTypeVersion="1.0.0"
                     xmlns="http://schemas.microsoft.com/2011/01/fabric"
                     xmlns:xsd="https://www.w3.org/2001/XMLSchema"
                     xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
  <!-- Import the ServiceManifest from the ServicePackage. The ServiceManifestName and ServiceManifestVersion 
       should match the Name and Version attributes of the ServiceManifest element defined in the 
       ServiceManifest.xml file. -->
  <ServiceManifestImport>
    <ServiceManifestRef ServiceManifestName="myservicePkg" ServiceManifestVersion="1.0.0" />
    <ConfigOverrides />
    <Policies>
      <ContainerHostPolicies CodePackageRef="Code">
        <RepositoryCredentials AccountName="myregistry" Password="=P==/==/=8=/=+u4lyOB=+=nWzEeRfF=" PasswordEncrypted="false"/>
        <PortBinding ContainerPort="80" EndpointRef="myServiceTypeEndpoint"/>
      </ContainerHostPolicies>
    </Policies>
  </ServiceManifestImport>
  <DefaultServices>
    <!-- The section below creates instances of service types, when an instance of this 
         application type is created. You can also create one or more instances of service type using the 
         ServiceFabric PowerShell module.
         
         The attribute ServiceTypeName below must match the name defined in the imported ServiceManifest.xml file. -->
    <Service Name="myservice">
      <!-- On a local development cluster, set InstanceCount to 1. On a multi-node production 
      cluster, set InstanceCount to -1 for the container service to run on every node in 
      the cluster.
      -->
      <StatelessService ServiceTypeName="myserviceType" InstanceCount="1">
        <SingletonPartition />
      </StatelessService>
    </Service>
  </DefaultServices>
</ApplicationManifest>

既存アプリケーションへのサービスの追加

yeoman を使用して作成したアプリケーションに別のコンテナー サービスを追加するには、次の手順を実行します。

  1. ディレクトリを既存アプリケーションのルートに変更します。 たとえば、Yeoman で作成したアプリケーションが MyApplication の場合は、cd ~/YeomanSamples/MyApplication です。
  2. yo azuresfcontainer:AddService を実行します。

コンテナーを強制終了するまでの期間を構成する

サービスの削除 (または別のノードへの移動) が開始された後でコンテナーが削除されるまでにランタイムが待機する期間を構成できます。 期間を構成すると、docker stop <time in seconds> コマンドがコンテナーに送信されます。 詳細については、docker stop の説明を参照してください。 待機する期間は Hosting セクションで指定します。 次のクラスター マニフェストのスニペットは、待機間隔を設定する方法を示しています。

{
        "name": "Hosting",
        "parameters": [
          {
                "name": "ContainerDeactivationTimeout",
                "value" : "10"
          },
	      ...
        ]
}

既定の期間は 10 秒に設定されています。 この構成は動的であるため、クラスター上の構成のみをアップグレードするだけでタイムアウトが更新されます。

使用していないコンテナー イメージを削除するようにランタイムを構成する

使用していないコンテナー イメージをノードから削除するように Service Fabric クラスターを構成できます。 この構成により、ノード上に存在するコンテナー イメージが多すぎる場合にディスク領域を再取得できます。 この機能を有効にするには、次のスニペットに示すように、クラスター マニフェストの Hosting セクションを更新します。

{
        "name": "Hosting",
        "parameters": [
          {
                "name": "PruneContainerImages",
                "value": "True"
          },
          {
                "name": "ContainerImagesToSkip",
                "value": "mcr.microsoft.com/windows/servercore|mcr.microsoft.com/windows/nanoserver|mcr.microsoft.com/dotnet/framework/aspnet|..."
          }
          ...
          }
        ]
} 

削除してはならないイメージは ContainerImagesToSkip パラメーターに指定することができます。

コンテナー イメージのダウンロード時間を構成する

Service Fabric ランタイムはコンテナー イメージのダウンロードと抽出に 20 分を割り当てており、これは大部分のコンテナー イメージにとって十分な時間です。 大きなイメージの場合、またはネットワーク接続の速度が遅い場合、イメージのダウンロードおよび抽出が中止されるまでの待機時間を長くすることが必要になる場合があります。 このタイムアウトは、次のスニペットに示すように、クラスター マニフェストの Hosting セクションの ContainerImageDownloadTimeout 属性を使用して設定できます。

{
        "name": "Hosting",
        "parameters": [
          {
              "name": "ContainerImageDownloadTimeout",
              "value": "1200"
          }
        ]
}

コンテナーの保持ポリシーを設定する

コンテナーのスタートアップ エラーの診断を支援するために、Service Fabric (バージョン 6.1 以降) では、終了または起動に失敗したコンテナーの保持をサポートしています。 このポリシーは、次のスニペットで示すように、ApplicationManifest.xml ファイルで設定できます。

 <ContainerHostPolicies CodePackageRef="NodeService.Code" Isolation="process" ContainersRetentionCount="2"  RunInteractive="true"> 

ContainersRetentionCount の設定で、エラーが発生したときに保持するコンテナーの数を指定します。 負の値が指定されている場合は、エラーが発生したすべてのコンテナーが保持されます。 ContainersRetentionCount 属性が指定されていない場合、コンテナーは保持されません。 ContainersRetentionCount 属性はアプリケーション パラメーターもサポートしているため、ユーザーはテスト クラスターと運用環境クラスターで別の値を指定できます。 コンテナー サービスが他のノードに移動することを防ぐためにこの機能を使用する場合は、配置の制約を使用して、コンテナー サービスの対象を特定のノードに制約してください。 この機能を使用して保持されるコンテナーは、手動で削除する必要があります。

カスタム引数で Docker デーモンを起動する

Service Fabric ランタイムの 6.2 バージョン以降では、カスタム引数を使って Docker デーモンを起動することができます。 カスタム引数が指定された場合、Service Fabric は、他の引数を一切 Docker エンジンに渡しませんが、--pidfile 引数は例外です。 したがって、--pidfile を引数として渡すことは避けてください。 また、Service Fabric が Docker デーモンと通信できるようにするために、Docker デーモンに引き続き既定の名前付きパイプ (Windows の場合) または UNIX ドメイン ソケット (Linux の場合) でリッスンさせるよう引数で指定する必要があります。 カスタム引数は、クラスター マニフェストの Hosting セクションの ContainerServiceArguments で指定します。 その例を次のスニペットに示します。

{ 
        "name": "Hosting", 
        "parameters": [ 
          { 
            "name": "ContainerServiceArguments", 
            "value": "-H localhost:1234 -H unix:///var/run/docker.sock" 
          } 
        ] 
} 

次の手順