В этой статье мы подготовим окружение для запуска контейнеров в Windows 10 и создадим простое контейнеризированное .NET приложение
Чтобы все описанные ниже действия были успешно выполнены, потребуется 64-разрядная система с версией не меньше 2004 и сборкой не меньше 18362. Проверим версию и номер сборки, выполнив в PowerShell команду winver
![](https://habrastorage.org/getpro/habr/upload_files/922/725/19f/92272519f08d939ffcdf566cbfa6f6e6.png)
Если версия ниже требуемой, то необходимо произвести обновление и только после этого идти дальше
Установка WSL 2
Сначала включим компонент Windows Subsystem for Linux (WSL). Для этого запустим PowerShell с правами администратора и выполним первую команду
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
![](https://habrastorage.org/getpro/habr/upload_files/1a7/88b/563/1a788b563ff72987008fc1253d43b5a2.png)
Выполним следующую команду
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
![](https://habrastorage.org/getpro/habr/upload_files/3eb/6bc/59d/3eb6bc59df81576e3b5074fcc94a716d.png)
Чтобы завершить установку, перезагрузим компьютер shutdown -r -t 1
Установим пакет обновления ядра Linux
Выберем WSL 2 по умолчанию для новых дистрибутивов Linux wsl --set-default-version 2
![](https://habrastorage.org/getpro/habr/upload_files/588/afc/2b6/588afc2b6cdd45b96fe576c1c06535b4.png)
Для целей этой статьи это необязательно, но установим дистрибутив Linux через Microsoft Store, например, Ubuntu 20.04 LTS
![](https://habrastorage.org/getpro/habr/upload_files/24e/2e0/1f2/24e2e01f2ad1d837cdca4fcfcd71e5b2.png)
При первом запуске установленного дистрибутива введем имя пользователя и пароль
![](https://habrastorage.org/getpro/habr/upload_files/373/d34/8e7/373d348e70e6d5224802b38bfbd4b4cf.png)
Чтобы увидеть запущенные дистрибутивы Linux, выполним в PowerShell команду wsl --list --verbose
![](https://habrastorage.org/getpro/habr/upload_files/2fb/f26/959/2fbf26959d96c009b28619c2dab6dc27.png)
Чтобы завершить работу дистрибутива Linux, выполним команду wsl --terminate Ubuntu-20.04
![](https://habrastorage.org/getpro/habr/upload_files/12a/ed7/913/12aed79133672838c9e42198db1d75a4.png)
Файловая система запущенного дистрибутива Linux будет смонтирована по этому пути \\wsl$
Более подробно о подсистеме WSL
Более подробно об установке подсистемы WSL и устранение неполадок
Установка Docker
Скачаем Docker Desktop для Windows и установим, следуя простым инструкциям
![](https://habrastorage.org/getpro/habr/upload_files/fe9/d9c/126/fe9d9c1262128f83fa720d8a39d86e32.png)
После установки запустим приложение Docker Desktop и установим интеграцию Docker с дистрибутивом Linux (WSL 2)
![](https://habrastorage.org/getpro/habr/upload_files/4ae/ea7/bc0/4aeea7bc02277c7717a990fe0bf836e7.png)
Теперь отправлять команды Docker можно как через PowerShell, так и через Bash. Выполним команду docker version
![](https://habrastorage.org/getpro/habr/upload_files/d70/1d9/88c/d701d988cac2590df0a16bc279707cc5.png)
Более подробно о Docker Desktop
Запуск контейнеров
Чтобы убедиться, что Docker правильно установлен и работает должным образом, запустим простой контейнер busybox, который всего лишь выведет в консоль переданное сообщение и завершит свое выполнение
docker run busybox echo "hello docker!!!"
![](https://habrastorage.org/getpro/habr/upload_files/2d7/f1f/e34/2d7f1fe343b6d88e0f659474fbc26d36.png)
Хорошо. Давайте сделаем что-то более интересное. Например, запустим контейнер rabbitmq
docker run --name rabbit1 -p 8080:15672 -p 5672:5672 rabbitmq:3.8.9-management
![](https://habrastorage.org/getpro/habr/upload_files/fb7/164/b63/fb7164b6371be20f2f2a760e6611a372.png)
Разберем выполненную команду:
docker run
- запускает контейнер из образа. Если данный образ отсутствует локально, то предварительно он будет загружен из репозитория Docker Hub
--name rabbit1
- присваивает запускаемому контейнеру имя rabbit1
-p 8080:15672
- пробрасывает порт с хоста в контейнер. 8080 - порт на хосте, 15672 - порт в контейнере
rabbitmq:3.8.9-management
- имя образа и его тег/версия, разделенные двоеточием
Теперь мы можем извне контейнера взаимодействовать с сервером RabbitMQ через порт 5672 и получить доступ к управлению из браузера через порт 8080
![](https://habrastorage.org/getpro/habr/upload_files/ec2/a99/e91/ec2a99e917ca083dcf38f5cfa3abe6e9.png)
Посмотреть статус контейнеров, в том числе остановленных, можно с помощью команд docker container ls --all
или docker ps -a
![](https://habrastorage.org/getpro/habr/upload_files/c4c/754/536/c4c7545368b90394844f7add718eaeaa.png)
Чтобы остановить наш контейнер: docker stop rabbit1
. Запустить вновь: docker start rabbit1
Более подробно о командах Docker
Отладка .NET приложения запущенного в контейнере
Для нашего примера нам понадобится отдельная сеть, т.к. мы запустим целых два контейнера, которые будут взаимодействовать между собой. На самом деле все запускаемые контейнеры по умолчанию попадают в уже существующую сеть с именем bridge, но т.к. в своей сети мы без лишних проблем сможешь обращаться из одного контейнера к другому прямо по имени, создадим сеть с названием mynet типа bridge
docker network create mynet
![](https://habrastorage.org/getpro/habr/upload_files/68b/cfe/6cf/68bcfe6cfe75a65b7458455c5600c500.png)
Далее запустим redis и подключим его к ранее созданной сети. Благодаря параметру -d
процесс в контейнере будет запущен в фоновом режиме
docker run --name redis1 --network mynet -d redis
![](https://habrastorage.org/getpro/habr/upload_files/dec/632/bda/dec632bdae8933528928f506b5733f32.png)
Далее с помощью Visual Studio 2019 создадим новый проект ASP.NET Core Web API, который будет использован для демонстрации отладки
![](https://habrastorage.org/getpro/habr/upload_files/297/8d3/43f/2978d343fb831b05961fe9220fa02433.png)
Добавим для взаимодействия с Redis пакет StackExchange.Redis через Package Manager Console
Install-Package StackExchange.Redis -Version 2.2.4
Мы не будем акцентироваться на правильности и красоте дизайна, а быстро создадим рабочий пример
Добавим в проект файл RandomWeatherService.cs, где будет находится служба для выдачи не очень точного прогноза
using System;
namespace WebApiFromDocker
{
public class RandomWeatherService
{
private Random _randomGenerator;
public RandomWeatherService()
{
_randomGenerator = new Random();
}
public int GetForecast(string city)
{
var length = city.Length;
var temperatureC = _randomGenerator.Next(-length, length);
return temperatureC;
}
}
}
Добавим файл RedisRepository.cs, где будет находится служба кеширования сформированных прогнозов
using StackExchange.Redis;
using System;
using System.Threading.Tasks;
namespace WebApiFromDocker
{
public class RedisRepository
{
private string _connectionString = "redis1:6379";
private TimeSpan _expiry = TimeSpan.FromHours(1);
public async Task SetValue(string key, string value)
{
using var connection = await ConnectionMultiplexer
.ConnectAsync(_connectionString);
var db = connection.GetDatabase();
await db.StringSetAsync(key.ToUpper(), value, _expiry);
}
public async Task<string> GetValue(string key)
{
using var connection = await ConnectionMultiplexer
.ConnectAsync(_connectionString);
var db = connection.GetDatabase();
var redisValue = await db.StringGetAsync(key.ToUpper());
return redisValue;
}
}
}
Зарегистрируем созданные службы в классе Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<RandomWeatherService>();
services.AddScoped<RedisRepository>();
services.AddControllers();
}
И наконец, изменим созданный автоматически единственный контроллер WeatherForecastController следующим образом
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
namespace WebApiFromDocker.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private RandomWeatherService _weather;
private RedisRepository _cache;
public WeatherForecastController(
RandomWeatherService weather,
RedisRepository cache)
{
_weather = weather;
_cache = cache;
}
//GET /api/weatherforecast/moscow
[HttpGet("{city}")]
public async Task<WeatherForecast> GetAsync(string city)
{
int temperatureC;
var cachedTemperatureCString = await _cache.GetValue(city);
if (!string.IsNullOrEmpty(cachedTemperatureCString))
{
temperatureC = Convert.ToInt32(cachedTemperatureCString);
}
else
{
temperatureC = _weather.GetForecast(city);
await _cache.SetValue(city, temperatureC.ToString());
}
var forecast = new WeatherForecast(
city, DateTime.UtcNow, temperatureC);
return forecast;
}
}
}
Помимо прочего в проект автоматически был добавлен файл Dockerfile с инструкциями для Docker. Оставим его без изменений
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
WORKDIR /src
COPY ["WebApiFromDocker/WebApiFromDocker.csproj", "WebApiFromDocker/"]
RUN dotnet restore "WebApiFromDocker/WebApiFromDocker.csproj"
COPY . .
WORKDIR "/src/WebApiFromDocker"
RUN dotnet build "WebApiFromDocker.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "WebApiFromDocker.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApiFromDocker.dll"]
В результате получим следующую структуру проекта
Если по какой-то невероятной причине Вам понадобятся исходники, то они здесь
Запустим наше приложение в контейнере под отладкой
После того как контейнер будет запущен, также подключим его к сети mynet
docker network connect mynet WebApiFromDocker
![](https://habrastorage.org/getpro/habr/upload_files/10c/c41/09a/10cc4109afea08d32cab600aeae9b6ab.png)
После убедимся, что все необходимые контейнеры находятся в одной сети
docker network inspect mynet
![](https://habrastorage.org/getpro/habr/upload_files/23e/b87/87b/23eb8787bd5b6a282652d2c2edd09731.png)
Далее установим Breakpoint в единственном методе контроллера и пошлем запрос через Postman, или через любой браузер
http://localhost:49156/api/weatherforecast/moscow
![](https://habrastorage.org/getpro/habr/upload_files/a39/a48/186/a39a4818638e8094c316841e0b5f0c05.png)
Кстати, используемый порт в Вашем случае может отличаться и его можно посмотреть в окне Containers
Результат в окне Postman
![](https://habrastorage.org/getpro/habr/upload_files/a5b/857/71f/a5b85771fd54d176dd48002136fcd741.png)
Дополнительно убедимся, что значение зафиксировано в redis, подключившись с помощью консоли redis-cli
![](https://habrastorage.org/getpro/habr/upload_files/b52/13a/265/b5213a2655c92b7e333c09d8cf3ddb55.png)
Хорошо, все сработало, как и задумано!
ebragim
Ошибка в начале: вы два раза одby модуль активируете featurename:VirtualMachinePlatform
sasha654 Автор
Спасибо, поправил