如何确定在Docker容器中运行的PostgreSQL是否已通过Marten初始化完成? [英] How to determine whether PostgreSQL running in a Docker container is done initializing with Marten?

查看:92
本文介绍了如何确定在Docker容器中运行的PostgreSQL是否已通过Marten初始化完成?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我基本上是写一些程序来测试给定表增长情况下 PostgreSQL 的插入性能,我想确保使用Marten插入数据,数据库已准备就绪,可以接受插入。

I am basically writing a little bit of a program to benchmark the insertion performance of PostgreSQL over a given table growth, and I would like to make sure that when I am using Marten to insert data, the database is fully ready to accept insertions.

我正在使用Docker.DotNet生成运行最新的新容器PostgreSQL 映像,但是即使容器处于运行状态,有时Postgre在该容器中运行也不是这种情况。

I am using Docker.DotNet to spawn a new container running the latest PostgreSQL image but even if the container is in a running state it is sometimes not the case for the Postgre running inside that container.

当然,我可以添加一个 Thread。睡眠,但是这有点随机,所以我希望确定性地找出数据库何时准备接受插入?

Of course, I could have added a Thread. Sleep but this is a bit random so I would rather like something deterministic to figure out when the database is ready to accept insertions?

public static class Program
{
    public static async Task Main(params string[] args)
    {
        const string imageName = "postgres:latest";
        const string containerName = "MyProgreSQL";

        var client = new DockerClientConfiguration(Docker.DefaultLocalApiUri).CreateClient();
        var containers = await client.Containers.SearchByNameAsync(containerName);

        var container = containers.SingleOrDefault();
        if (container != null)
        {
            await client.Containers.StopAndRemoveContainerAsync(container);
        }

        var createdContainer = await client.Containers.RunContainerAsync(new CreateContainerParameters
        {
            Image = imageName,

            HostConfig = new HostConfig
            {
                PortBindings = new Dictionary<string, IList<PortBinding>>
                {
                    {"5432/tcp", new List<PortBinding>
                    {
                        new PortBinding
                        {
                            HostPort = "5432"
                        }
                    }}
                },
                PublishAllPorts = true
            },
            Env = new List<string>
            {
                "POSTGRES_PASSWORD=root",
                "POSTGRES_USER=root"
            },
            Name = containerName
        });

        var containerState = string.Empty;
        while (containerState != "running")
        {
            containers = await client.Containers.SearchByNameAsync(containerName);
            container = containers.Single();
            containerState = container.State;
        }

        var store = DocumentStore.For("host=localhost;database=postgres;password=root;username=root");

        var stopwatch = new Stopwatch();
        using (var session = store.LightweightSession())
        {
            var orders = OrderHelpers.FakeOrders(10000);
            session.StoreObjects(orders);
            stopwatch.Start();
            await session.SaveChangesAsync();
            var elapsed = stopwatch.Elapsed;
            // Whatever else needs to be done...
        }
    }
}

仅供参考,如果我正在运行上面的程序而在 await session.SaveChangesAsync();行之前没有暂停,然后运行以下异常:

FYI, the if I am running the program above without pausing before the line await session.SaveChangesAsync(); I am running than into the following exception:

Unhandled Exception: Npgsql.NpgsqlException: Exception while reading from stream ---> System.IO.EndOfStreamException: Attempted to read past the end of the streams.
   at Npgsql.NpgsqlReadBuffer.<>c__DisplayClass31_0.<<Ensure>g__EnsureLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlReadBuffer.cs:line 154
   --- End of inner exception stack trace ---
   at Npgsql.NpgsqlReadBuffer.<>c__DisplayClass31_0.<<Ensure>g__EnsureLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlReadBuffer.cs:line 175
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlConnector.<>c__DisplayClass161_0.<<ReadMessage>g__ReadMessageLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.cs:l
ine 955
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlConnector.Authenticate(String username, NpgsqlTimeout timeout, Boolean async) in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.Auth.cs
:line 26
   at Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlConne
ctor.cs:line 425
   at Npgsql.ConnectorPool.AllocateLong(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) in C:\projects\
npgsql\src\Npgsql\ConnectorPool.cs:line 246
   at Npgsql.NpgsqlConnection.<>c__DisplayClass32_0.<<Open>g__OpenLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlConnection.cs:line 300
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlConnection.Open() in C:\projects\npgsql\src\Npgsql\NpgsqlConnection.cs:line 153
   at Marten.Storage.Tenant.generateOrUpdateFeature(Type featureType, IFeatureSchema feature)
   at Marten.Storage.Tenant.ensureStorageExists(IList`1 types, Type featureType)
   at Marten.Storage.Tenant.ensureStorageExists(IList`1 types, Type featureType)
   at Marten.Storage.Tenant.StorageFor(Type documentType)
   at Marten.DocumentSession.Store[T](T[] entities)
   at Baseline.GenericEnumerableExtensions.Each[T](IEnumerable`1 values, Action`1 eachAction)
   at Marten.DocumentSession.StoreObjects(IEnumerable`1 documents)
   at Benchmark.Program.Main(String[] args) in C:\Users\eperret\Desktop\Benchmark\Benchmark\Program.cs:line 117
   at Benchmark.Program.<Main>(String[] args)

我接受了一个答案,但是由于 Docker中有关健康参数等效的错误.DotNet 我无法利用答案中给出的解决方案(我仍然认为,如果可能的话,.NET客户端中该docker命令的正确翻译)将是最佳解决方案。同时,这就是我解决问题的方式,基本上我将在运行状况检查中运行的命令轮询到一边,直到结果正常为止。

I accepted an answer but due to a bug about health parameters equivalence in the Docker.DotNet I could not leverage the solution given in the answer (I still think that a proper translation of that docker command in the .NET client, if actually possible) would be the best solution. In the meanwhile this is how I solved my problem, I basically poll the command that was expected to run in the health check aside until the result is ok:

Program.cs ,代码的实际内容:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Benchmark.DockerClient;
using Benchmark.Domain;
using Benchmark.Utils;
using Docker.DotNet;
using Docker.DotNet.Models;
using Marten;
using Microsoft.Extensions.Configuration;

namespace Benchmark
{
    public static class Program
    {
        public static async Task Main(params string[] args)
        {
            var configurationBuilder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");

            var configuration = new Configuration();
            configurationBuilder.Build().Bind(configuration);

            var client = new DockerClientConfiguration(DockerClient.Docker.DefaultLocalApiUri).CreateClient();
            var containers = await client.Containers.SearchByNameAsync(configuration.ContainerName);

            var container = containers.SingleOrDefault();
            if (container != null)
            {
                await client.Containers.StopAndRemoveContainerAsync(container.ID);
            }

            var createdContainer = await client.Containers.RunContainerAsync(new CreateContainerParameters
            {
                Image = configuration.ImageName,
                HostConfig = new HostConfig
                {
                    PortBindings = new Dictionary<string, IList<PortBinding>>
                    {
                        {$@"{configuration.ContainerPort}/{configuration.ContainerPortProtocol}", new List<PortBinding>
                        {
                            new PortBinding
                            {
                                HostPort = configuration.HostPort
                            }
                        }}
                    },
                    PublishAllPorts = true
                },
                Env = new List<string>
                {
                    $"POSTGRES_USER={configuration.Username}",
                    $"POSTGRES_PASSWORD={configuration.Password}"
                },
                Name = configuration.ContainerName
            });

            var isContainerReady = false;

            while (!isContainerReady)
            {
                var result = await client.Containers.RunCommandInContainerAsync(createdContainer.ID, "pg_isready -U postgres");
                if (result.stdout.TrimEnd('\n') == $"/var/run/postgresql:{configuration.ContainerPort} - accepting connections")
                {
                    isContainerReady = true;
                }
            }

            var store = DocumentStore.For($"host=localhost;" +
                                          $"database={configuration.DatabaseName};" +
                                          $"username={configuration.Username};" +
                                          $"password={configuration.Password}");

            // Whatever else needs to be done...
    }
}

ContainerOperationsExtensions.cs 中定义的扩展名:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet;
using Docker.DotNet.Models;

namespace Benchmark.DockerClient
{
    public static class ContainerOperationsExtensions
    {
        public static async Task<IList<ContainerListResponse>> SearchByNameAsync(this IContainerOperations source, string name, bool all = true)
        {
            return await source.ListContainersAsync(new ContainersListParameters
            {
                All = all,
                Filters = new Dictionary<string, IDictionary<string, bool>>
                {
                    {"name", new Dictionary<string, bool>
                        {
                            {name, true}
                        }
                    }
                }
            });
        }

        public static async Task StopAndRemoveContainerAsync(this IContainerOperations source, string containerId)
        {
            await source.StopContainerAsync(containerId, new ContainerStopParameters());
            await source.RemoveContainerAsync(containerId, new ContainerRemoveParameters());
        }

        public static async Task<CreateContainerResponse> RunContainerAsync(this IContainerOperations source, CreateContainerParameters parameters)
        {
            var createdContainer = await source.CreateContainerAsync(parameters);
            await source.StartContainerAsync(createdContainer.ID, new ContainerStartParameters());
            return createdContainer;
        }

        public static async Task<(string stdout, string stderr)> RunCommandInContainerAsync(this IContainerOperations source, string containerId, string command)
        {
            var commandTokens = command.Split(" ", StringSplitOptions.RemoveEmptyEntries);

            var createdExec = await source.ExecCreateContainerAsync(containerId, new ContainerExecCreateParameters
            {
                AttachStderr = true,
                AttachStdout = true,
                Cmd = commandTokens
            });

            var multiplexedStream = await source.StartAndAttachContainerExecAsync(createdExec.ID, false);

            return await multiplexedStream.ReadOutputToEndAsync(CancellationToken.None);
        }
    }
}

Docker.cs 来获取本地docker api uri:

Docker.cs to get the local docker api uri:

using System;
using System.Runtime.InteropServices;

namespace Benchmark.DockerClient
{
    public static class Docker
    {
        static Docker()
        {
            DefaultLocalApiUri = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 
                ? new Uri("npipe://./pipe/docker_engine")
                : new Uri("unix:/var/run/docker.sock");
        }

        public static Uri DefaultLocalApiUri { get; }
    }
}


推荐答案

我建议您使用自定义体检来检查数据库是否已准备就绪接受连接。

I suggest you to use a custom healtcheck to check if the database is ready to accept connections.

我不熟悉Docker的.NET客户端,但是下面的 docker run 命令显示您应该尝试的方法:

I am not familiar with the .NET client of Docker, but the following docker run command shows what you should try :

docker run --name postgres \
    --health-cmd='pg_isready -U postgres' \
    --health-interval='10s' \
    --health-timeout='5s' \
    --health-start-period='10s' \
    postgres:latest

时间参数应根据您的需要进行更新。

Time parameters should be updated accordingly to your needs.

配置了healtcheck后,您的应用程序必须等待容器进入状态 健康,然后才能尝试连接数据库。在这种情况下,状态为 健康表示命令 pg_isready -U postgres 已成功执行(因此数据库已准备好接受连接)。

With this healtcheck configured, your application must wait for the container to be in state "healthy" before trying to connect to the database. The status "healthy", in that particular case, means that the command pg_isready -U postgres have succeeded (so the database is ready to accept connections).

可以通过以下方式检索容器的状态:

The status of your container can be retrieved with :

docker inspect --format "{{json .State.Health.Status }}" postgres

这篇关于如何确定在Docker容器中运行的PostgreSQL是否已通过Marten初始化完成?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆