Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
REST-сервисы на
ASP.NET Core под Linux в
продакшене
Денис Иванов
denis@ivanovdenis.ru
@denisivanov
1
Обо мне
2
Цель
Поделиться опытом разработки и запуска в
продакшен REST-сервисов на ASP.NET Core на
Kubernetes
3
План
-Коротко о сервисе
-On-premise платформа
-.NET Core, ASP.NET Core, базовые фичи
-Билд
-Деплой
-Нагрузочное тестирован...
Коротко о сервисе
5
Сервис видеорекламы
6
Сервис видеорекламы
-99.99% доступность по миру
-Время ответа 200ms*
7
Сервис видеорекламы
8
Сервис видеорекламы
9
Почему Linux
-Cуществующая on-premise платформа
•GitLab CI
•CI starting kit на основе make
•Docker hub & docker images
-Ко...
The Twelve-Factor App (1-6)
-Одно приложение – один репозиторий
-Зависимости – вместе с приложением
-Конфигурация через ок...
The Twelve-Factor App (7-12)
-Port binding
-Масштабирование через процессы
-Быстрая остановка и запуск процессов
-Среды ма...
Код и презентация
https://github.com/denisivan0v/backend-conf-2017
13
-Коротко о сервисе
-On-premise платформа
-.NET Core, ASP.NET Core, базовые фичи
-Билд
-Деплой
-Нагрузочное тестирование
-P...
.NET Core
15http://www.telerik.com/blogs/the-new-net-core-1-is-here
.NET Core
16https://blogs.windows.com/buildingapps/2015/04/29/expanding-the-universal-windows-platform-at-build-2015/
.NET Core
17http://www.telerik.com/blogs/the-new-net-core-1-is-here
.NET Core
18http://www.c-sharpcorner.com/article/getting-started-with-net-core-on-visual-studio-2017/
.NET Core. Self-contained deployment
-Полный контроль зависимостей
-Явное указание платформы при билде
(win10-x64 / ubuntu...
ASP.NET Core
20
public sealed class HealthCheckMiddleware
{
private const string Path = "/healthcheck";
private readonly RequestDelegate _...
public sealed class HealthCheckMiddleware
{
private const string Path = "/healthcheck";
private readonly RequestDelegate _...
public sealed class HealthCheckMiddleware
{
private const string Path = "/healthcheck";
private readonly RequestDelegate _...
public sealed class HealthCheckMiddleware
{
private const string Path = "/healthcheck";
private readonly RequestDelegate _...
public sealed class HealthCheckMiddleware
{
private const string Path = "/healthcheck";
private readonly RequestDelegate _...
Базовые фичи REST-сервисов
-Логирование
•Структурное логирование
-Версионирование API
•SemVer
•DateTime
-Формальное описан...
Структурное логирование
27
https://serilog.net/
appsettings.json
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter":
"Serilog...
appsettings.json
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter":
"Serilog...
appsettings.json
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter":
"Serilog...
appsettings.json
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter":
"Serilog...
appsettings.json
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter":
"Serilog...
ASP.NET API versioning
- https://github.com/Microsoft/aspnet-api-versioning
- Microsoft REST versioning guidelines
• /api/...
[ApiVersion("1.0")]
[Route(“api/medias")] // /api/medias
[Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias
pub...
[ApiVersion("1.0")]
[Route(“api/medias")] // /api/medias
[Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias
pub...
[ApiVersion("1.0")]
[Route(“api/medias")] // /api/medias
[Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias
pub...
[ApiVersion("1.0")]
[Route(“api/medias")] // /api/medias
[Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias
pub...
[ApiVersion("1.0")]
[Route(“api/medias")] // /api/medias
[Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias
pub...
[ApiVersion("1.0")]
[ApiVersion(“2.0")]
[Route(“api/medias")]
[Route(“api/{version:apiVersion}/medias")]
public sealed cla...
[ApiVersion("1.0")]
[ApiVersion(“2.0")]
[Route(“api/medias")]
[Route(“api/{version:apiVersion}/medias")]
public sealed cla...
[ApiVersion("1.0")]
[ApiVersion(“2.0")]
[Route(“api/medias")]
[Route(“api/{version:apiVersion}/medias")]
public sealed cla...
https://github.com/domaindrivendev/Swashbuckle.AspNetCore
42
services.AddSwaggerGen(
x =>
{
IApiVersionDescriptionProvider provider;
foreach (var description in provider.ApiVersionDes...
services.AddSwaggerGen(
x =>
{
IApiVersionDescriptionProvider provider;
foreach (var description in provider.ApiVersionDes...
services.AddSwaggerGen(
x =>
{
IApiVersionDescriptionProvider provider;
foreach (var description in provider.ApiVersionDes...
-Коротко о сервисе
-On-premise платформа
-.NET Core, ASP.NET Core, базовые фичи
-Билд
-Деплой
-Нагрузочное тестирование
-P...
build:backend-conf-demo:
image: $REGISTRY/microsoft/aspnetcore-build:1.1.2
stage: build:app
script:
- dotnet restore --run...
build:backend-conf-demo:
image: $REGISTRY/microsoft/aspnetcore-build:1.1.2
stage: build:app
script:
- dotnet restore --run...
build:backend-conf-demo:
image: $REGISTRY/microsoft/aspnetcore-build:1.1.2
stage: build:app
script:
- dotnet restore --run...
build:backend-conf-demo:
image: $REGISTRY/microsoft/aspnetcore-build:1.1.2
stage: build:app
script:
- dotnet restore --run...
build:backend-conf-demo:
image: $REGISTRY/microsoft/aspnetcore-build:1.1.2
stage: build:app
script:
- dotnet restore --run...
build:backend-conf-demo-image:
stage: build:app
script:
- IMAGE=my-namespace/backend-conf TAG=$CI_TAG
DOCKER_FILE=publish/...
build:backend-conf-demo-image:
stage: build:app
script:
- IMAGE=my-namespace/backend-conf TAG=$CI_TAG
DOCKER_FILE=publish/...
build:backend-conf-demo-image:
stage: build:app
script:
- IMAGE=my-namespace/backend-conf TAG=$CI_TAG
DOCKER_FILE=publish/...
build:backend-conf-demo-image:
stage: build:app
script:
- IMAGE=my-namespace/backend-conf TAG=$CI_TAG
DOCKER_FILE=publish/...
-Коротко о сервисе
-On-premise платформа
-.NET Core, ASP.NET Core, базовые фичи
-Билд
-Деплой
-Нагрузочное тестирование
-P...
Kubernetes
57
Kubernetes
58
Kubernetes
59
Kubernetes
60
Kubernetes
61
62
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ app_name }}
spec:
replicas: {{ replicas_count }}
templa...
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ app_name }}
spec:
replicas: {{ replicas_count }}
templa...
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ app_name }}
spec:
replicas: {{ replicas_count }}
templa...
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ app_name }}
spec:
replicas: {{ replicas_count }}
templa...
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ app_name }}
spec:
replicas: {{ replicas_count }}
templa...
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ app_name }}
spec:
replicas: {{ replicas_count }}
templa...
apiVersion: v1
kind: Service
metadata:
name: {{ app_name }}
annotations:
router.deis.io/domains: "{{ app_name }}"
router.d...
apiVersion: v1
kind: Service
metadata:
name: {{ app_name }}
annotations:
router.deis.io/domains: "{{ app_name }}"
router.d...
apiVersion: v1
kind: Service
metadata:
name: {{ app_name }}
annotations:
router.deis.io/domains: "{{ app_name }}"
router.d...
common:
replicas_count: 1
max_unavailable: 0
k8s_master_uri: https://master.staging.dc-nsk1.hw:6443
k8s_token: "{{ env='K8...
common:
replicas_count: 1
max_unavailable: 0
k8s_master_uri: https://master.staging.dc-nsk1.hw:6443
k8s_token: "{{ env='K8...
common:
replicas_count: 1
max_unavailable: 0
k8s_master_uri: https://master.staging.dc-nsk1.hw:6443
k8s_token: "{{ env='K8...
common:
replicas_count: 1
max_unavailable: 0
k8s_master_uri: https://master.staging.dc-nsk1.hw:6443
k8s_token: "{{ env='K8...
deploy:backend-conf-demo-stage:
stage: deploy:stage
when: manual
image: $REGISTRY/2gis-io/k8s-handle:latest
script:
- expo...
deploy:backend-conf-demo-stage:
stage: deploy:stage
when: manual
image: $REGISTRY/2gis-io/k8s-handle:latest
script:
- expo...
-Коротко о сервисе
-On-premise платформа
-.NET Core, ASP.NET Core, базовые фичи
-Билд
-Деплой
-Нагрузочное тестирование
-P...
Нагрузочный контур
79
80
81
82
83
.perf:template: &perf_template
stage: test:perf
environment: perf
only:
- master
- /^perf.*$/
variables:
PERF_TEST_PATH: "...
perf:run-tests:
<<: *perf_template
script:
- export PERF_GRAPHITE_ROOT_PATH_PREFIX
PERF_GRAPHITE_HOST
- export PERF_APP_HO...
perf:run-tests:
<<: *perf_template
script:
- export PERF_GRAPHITE_ROOT_PATH_PREFIX
PERF_GRAPHITE_HOST
- export PERF_APP_HO...
perf:run-tests:
<<: *perf_template
script:
- export PERF_GRAPHITE_ROOT_PATH_PREFIX
PERF_GRAPHITE_HOST
- export PERF_APP_HO...
-Коротко о сервисе
-On-premise платформа
-.NET Core, ASP.NET Core, базовые фичи
-Билд
-Деплой
-Нагрузочное тестирование
-P...
Кеширование
89
[AllowAnonymous]
[HttpGet("{id}")]
[ResponseCache(
VaryByQueryKeys = new[] { "api-version" },
Duration = 36...
Кеширование
-На клиенте
•Cache-Control header (HTTP 1.1 Caching)
90
[ResponseCache(
VaryByQueryKeys = new[] { "api-version...
Кеширование
-На клиенте
•Cache-Control header (HTTP 1.1 Caching)
-На сервере
•Response Caching Middleware (docs)
91
[Respo...
Performance
92
Асинхронность Многопоточность
Performance
93
Thread1
Task1
Асинхронность
Task2Task1
Thread1
Task1
Многопоточность
Task2
Thread2
Task3Task4
94
var data =
await _remoteService.IOBoundOperationAsync(timeoutInSec: 1);
var result = new List<string>[data.Count];
fore...
95
var data =
await _remoteService.IOBoundOperationAsync(timeoutInSec: 1);
var result = new List<string>[data.Count];
fore...
96
var data =
await _remoteService.IOBoundOperationAsync(timeoutInSec: 1);
var result = new string[data.Count];
var tasks ...
97
var data =
await _remoteService.IOBoundOperationAsync(timeoutInSec: 1);
var result = new string[data.Count];
var tasks ...
98
var data =
await _remoteService.IOBoundOperationAsync(timeoutInSec: 1);
var result = new string[data.Count];
var tasks ...
Нагрузочное тестирование
-Лимиты по памяти и процессору
•384Mb и 1,5 CPU
99
Нагрузочное тестирование
-Лимиты по памяти и процессору
•384Mb и 1,5 CPU
-Синхронный (capacity) тест
•~24 RPS (без кэша)
1...
Нагрузочное тестирование
-Лимиты по памяти и процессору
•384Mb и 1,5 CPU
-Синхронный (capacity) тест
•~24 RPS (без кэша)
•...
Нагрузочное тестирование
-Лимиты по памяти и процессору
•384Mb и 1,5 CPU
-Синхронный (capacity) тест
•~24 RPS (без кэша)
•...
Вместо заключения
103
Вместо заключения
-Не бойтесь использовать .NET Core в
продакшене
104
Вместо заключения
-Не бойтесь использовать .NET Core в
продакшене
-Не бойтесь использовать Linux и .NET Core
105
Вместо заключения
-Не бойтесь использовать .NET Core в
продакшене
-Не бойтесь использовать Linux и .NET Core
-Docker и Kub...
Вместо заключения
-Не бойтесь использовать .NET Core в
продакшене
-Не бойтесь использовать Linux и .NET Core
-Docker и Kub...
Вместо заключения
-Не бойтесь использовать .NET Core в
продакшене
-Не бойтесь использовать Linux и .NET Core
-Docker и Kub...
Спасибо!
https://github.com/denisivan0v/backend-conf-2017
109
Вопросы?
Денис Иванов
@denisivanov
denis@ivanovdenis.ru
https://github.com/denisivan0v
110
Эксплуатация
111
Эксплуатация
-Prometheus server (/metrics)
112
Upcoming SlideShare
Loading in …5
×

REST-сервисы на ASP.NET Core под Linux в продакшене / Денис Иванов (2ГИС)

867 views

Published on

РИТ++ 2017, Backend Conf
Зал Кейптаун, 5 июня, 13:00

Тезисы:
http://backendconf.ru/2017/abstracts/2735.html

С релизом .NET Core для программистов, использующих .NET-стек, открылись все возможности Unix-мира. .NET-приложения могут отлично работать на Linux, а значит, мы можем использовать Docker и Kubernetes для развертывания сервисов.

В своем докладе я расскажу, как сделать REST-сервис на ASP.NET Core и запустить его в продакшн на платформе Kubernetes.

Мы погрузимся в детали инфраструктуры ASP.NET Core и нескольких популярных библиотек, поговорим про многопоточность, оптимизацию и кэширование для уменьшения времени ответа сервиса. Обсудим, как решать задачи билда приложения и сборки Docker-образов. И, конечно же, подробно остановимся на том, что такое Kubernetes, как эта технология может быть нам полезна и как ее использовать.

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

REST-сервисы на ASP.NET Core под Linux в продакшене / Денис Иванов (2ГИС)

  1. 1. REST-сервисы на ASP.NET Core под Linux в продакшене Денис Иванов denis@ivanovdenis.ru @denisivanov 1
  2. 2. Обо мне 2
  3. 3. Цель Поделиться опытом разработки и запуска в продакшен REST-сервисов на ASP.NET Core на Kubernetes 3
  4. 4. План -Коротко о сервисе -On-premise платформа -.NET Core, ASP.NET Core, базовые фичи -Билд -Деплой -Нагрузочное тестирование -Performance • Кэширование • Асинхронность и многопоточность 4
  5. 5. Коротко о сервисе 5
  6. 6. Сервис видеорекламы 6
  7. 7. Сервис видеорекламы -99.99% доступность по миру -Время ответа 200ms* 7
  8. 8. Сервис видеорекламы 8
  9. 9. Сервис видеорекламы 9
  10. 10. Почему Linux -Cуществующая on-premise платформа •GitLab CI •CI starting kit на основе make •Docker hub & docker images -Компоненты на любом технологическом стеке -Kubernetes 10
  11. 11. The Twelve-Factor App (1-6) -Одно приложение – один репозиторий -Зависимости – вместе с приложением -Конфигурация через окружение -Используемые сервисы как ресурсы -Фазы билда, создания образов и исполнения разделены -Сервисы – отдельные stateless процессы 11 https://12factor.net
  12. 12. The Twelve-Factor App (7-12) -Port binding -Масштабирование через процессы -Быстрая остановка и запуск процессов -Среды максимально похожи -Логирование в stdout -Административные процессы 12 https://12factor.net
  13. 13. Код и презентация https://github.com/denisivan0v/backend-conf-2017 13
  14. 14. -Коротко о сервисе -On-premise платформа -.NET Core, ASP.NET Core, базовые фичи -Билд -Деплой -Нагрузочное тестирование -Performance •Кэширование •Асинхронность и многопоточность 14
  15. 15. .NET Core 15http://www.telerik.com/blogs/the-new-net-core-1-is-here
  16. 16. .NET Core 16https://blogs.windows.com/buildingapps/2015/04/29/expanding-the-universal-windows-platform-at-build-2015/
  17. 17. .NET Core 17http://www.telerik.com/blogs/the-new-net-core-1-is-here
  18. 18. .NET Core 18http://www.c-sharpcorner.com/article/getting-started-with-net-core-on-visual-studio-2017/
  19. 19. .NET Core. Self-contained deployment -Полный контроль зависимостей -Явное указание платформы при билде (win10-x64 / ubuntu.16.04-x64 / osx.10.12-x64) -Только необходимый фреймворк netstandard1.6 •Microsoft.NETCore.Runtime.CoreCLR •Microsoft.NETCore.DotNetHostPolicy 19
  20. 20. ASP.NET Core 20
  21. 21. public sealed class HealthCheckMiddleware { private const string Path = "/healthcheck"; private readonly RequestDelegate _next; public HealthCheckMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (!context.Request.Path.Equals(Path, StringComparison.OrdinalIgnoreCase)) { await _next(context); } else { context.Response.ContentType = "text/plain"; context.Response.StatusCode = 200; context.Response.Headers.Add(HeaderNames.Connection, "close"); await context.Response.WriteAsync("OK"); } } } 21
  22. 22. public sealed class HealthCheckMiddleware { private const string Path = "/healthcheck"; private readonly RequestDelegate _next; public HealthCheckMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (!context.Request.Path.Equals(Path, StringComparison.OrdinalIgnoreCase)) { await _next(context); } else { context.Response.ContentType = "text/plain"; context.Response.StatusCode = 200; context.Response.Headers.Add(HeaderNames.Connection, "close"); await context.Response.WriteAsync("OK"); } } } 22
  23. 23. public sealed class HealthCheckMiddleware { private const string Path = "/healthcheck"; private readonly RequestDelegate _next; public HealthCheckMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (!context.Request.Path.Equals(Path, StringComparison.OrdinalIgnoreCase)) { await _next(context); } else { context.Response.ContentType = "text/plain"; context.Response.StatusCode = 200; context.Response.Headers.Add(HeaderNames.Connection, "close"); await context.Response.WriteAsync("OK"); } } } 23
  24. 24. public sealed class HealthCheckMiddleware { private const string Path = "/healthcheck"; private readonly RequestDelegate _next; public HealthCheckMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (!context.Request.Path.Equals(Path, StringComparison.OrdinalIgnoreCase)) { await _next(context); } else { context.Response.ContentType = "text/plain"; context.Response.StatusCode = 200; context.Response.Headers.Add(HeaderNames.Connection, "close"); await context.Response.WriteAsync("OK"); } } } 24
  25. 25. public sealed class HealthCheckMiddleware { private const string Path = "/healthcheck"; private readonly RequestDelegate _next; public HealthCheckMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (!context.Request.Path.Equals(Path, StringComparison.OrdinalIgnoreCase)) { await _next(context); } else { context.Response.ContentType = "text/plain"; context.Response.StatusCode = 200; context.Response.Headers.Add(HeaderNames.Connection, "close"); await context.Response.WriteAsync("OK"); } } } 25
  26. 26. Базовые фичи REST-сервисов -Логирование •Структурное логирование -Версионирование API •SemVer •DateTime -Формальное описание API •Swagger 26
  27. 27. Структурное логирование 27 https://serilog.net/
  28. 28. appsettings.json { "Serilog": { "MinimumLevel": "Debug", "WriteTo": [ { "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" } } ], "Enrich": [ "FromLogContext", "WithThreadId" ] } } 28
  29. 29. appsettings.json { "Serilog": { "MinimumLevel": "Debug", "WriteTo": [ { "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" } } ], "Enrich": [ "FromLogContext", "WithThreadId" ] } } 29
  30. 30. appsettings.json { "Serilog": { "MinimumLevel": "Debug", "WriteTo": [ { "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" } } ], "Enrich": [ "FromLogContext", "WithThreadId" ] } } 30
  31. 31. appsettings.json { "Serilog": { "MinimumLevel": "Debug", "WriteTo": [ { "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" } } ], "Enrich": [ "FromLogContext", "WithThreadId" ] } } 31
  32. 32. appsettings.json { "Serilog": { "MinimumLevel": "Debug", "WriteTo": [ { "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" } } ], "Enrich": [ "FromLogContext", "WithThreadId" ] } } 32
  33. 33. ASP.NET API versioning - https://github.com/Microsoft/aspnet-api-versioning - Microsoft REST versioning guidelines • /api/foo?api-version=1.0 • /api/foo?api-version=2.0-Alpha • /api/foo?api-version=2015-05-01.3.0 • /api/v1/foo • /api/v2.0-Alpha/foo • /api/v2015-05-01.3.0/foo 33
  34. 34. [ApiVersion("1.0")] [Route(“api/medias")] // /api/medias [Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias public sealed class MediasController : Controller { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } } 34
  35. 35. [ApiVersion("1.0")] [Route(“api/medias")] // /api/medias [Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias public sealed class MediasController : Controller { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } } 35
  36. 36. [ApiVersion("1.0")] [Route(“api/medias")] // /api/medias [Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias public sealed class MediasController : Controller { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } } 36
  37. 37. [ApiVersion("1.0")] [Route(“api/medias")] // /api/medias [Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias public sealed class MediasController : Controller { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } } 37
  38. 38. [ApiVersion("1.0")] [Route(“api/medias")] // /api/medias [Route(“api/{version:apiVersion}/medias")] // /api/1.0/medias public sealed class MediasController : Controller { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } } 38
  39. 39. [ApiVersion("1.0")] [ApiVersion(“2.0")] [Route(“api/medias")] [Route(“api/{version:apiVersion}/medias")] public sealed class MediasController : GatewayController { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } // /api/medias/id?api-version=2.0 or /api/2.0/medias/id [MapToApiVersion(“2.0")] [HttpGet("{id}")] public async Task<IActionResult> GetV2(long id) { … } } 39
  40. 40. [ApiVersion("1.0")] [ApiVersion(“2.0")] [Route(“api/medias")] [Route(“api/{version:apiVersion}/medias")] public sealed class MediasController : GatewayController { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } // /api/medias/id?api-version=2.0 or /api/2.0/medias/id [MapToApiVersion(“2.0")] [HttpGet("{id}")] public async Task<IActionResult> GetV2(long id) { … } } 40
  41. 41. [ApiVersion("1.0")] [ApiVersion(“2.0")] [Route(“api/medias")] [Route(“api/{version:apiVersion}/medias")] public sealed class MediasController : GatewayController { // /api/medias/id?api-version=1.0 or /api/1.0/medias/id [HttpGet("{id}")] public async Task<IActionResult> Get(long id) { … } // /api/medias/id?api-version=2.0 or /api/2.0/medias/id [MapToApiVersion(“2.0")] [HttpGet("{id}")] public async Task<IActionResult> GetV2(long id) { … } } 41
  42. 42. https://github.com/domaindrivendev/Swashbuckle.AspNetCore 42
  43. 43. services.AddSwaggerGen( x => { IApiVersionDescriptionProvider provider; foreach (var description in provider.ApiVersionDescriptions) { x.SwaggerDoc(description.GroupName, new Info { … }); } }); … app.UseSwagger(); app.UseSwaggerUI( c => { IApiVersionDescriptionProvider provider; foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); } }); 43
  44. 44. services.AddSwaggerGen( x => { IApiVersionDescriptionProvider provider; foreach (var description in provider.ApiVersionDescriptions) { x.SwaggerDoc(description.GroupName, new Info { … }); } }); … app.UseSwagger(); app.UseSwaggerUI( c => { IApiVersionDescriptionProvider provider; foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); } }); 44
  45. 45. services.AddSwaggerGen( x => { IApiVersionDescriptionProvider provider; foreach (var description in provider.ApiVersionDescriptions) { x.SwaggerDoc(description.GroupName, new Info { … }); } }); … app.UseSwagger(); app.UseSwaggerUI( c => { IApiVersionDescriptionProvider provider; foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); } }); 45
  46. 46. -Коротко о сервисе -On-premise платформа -.NET Core, ASP.NET Core, базовые фичи -Билд -Деплой -Нагрузочное тестирование -Performance •Кэширование •Асинхронность и многопоточность 46
  47. 47. build:backend-conf-demo: image: $REGISTRY/microsoft/aspnetcore-build:1.1.2 stage: build:app script: - dotnet restore --runtime ubuntu.16.04-x64 - dotnet test Demo.Tests/Demo.Tests.csproj --configuration Release - dotnet publish Demo --configuration Release --runtime ubuntu.16.04-x64 --output publish/backend-conf tags: [ 2gis, docker ] artifacts: paths: - publish/backend-conf/ 47
  48. 48. build:backend-conf-demo: image: $REGISTRY/microsoft/aspnetcore-build:1.1.2 stage: build:app script: - dotnet restore --runtime ubuntu.16.04-x64 - dotnet test Demo.Tests/Demo.Tests.csproj --configuration Release - dotnet publish Demo --configuration Release --runtime ubuntu.16.04-x64 --output publish/backend-conf tags: [ 2gis, docker ] artifacts: paths: - publish/backend-conf/ 48
  49. 49. build:backend-conf-demo: image: $REGISTRY/microsoft/aspnetcore-build:1.1.2 stage: build:app script: - dotnet restore --runtime ubuntu.16.04-x64 - dotnet test Demo.Tests/Demo.Tests.csproj --configuration Release - dotnet publish Demo --configuration Release --runtime ubuntu.16.04-x64 --output publish/backend-conf tags: [ 2gis, docker ] artifacts: paths: - publish/backend-conf/ 49
  50. 50. build:backend-conf-demo: image: $REGISTRY/microsoft/aspnetcore-build:1.1.2 stage: build:app script: - dotnet restore --runtime ubuntu.16.04-x64 - dotnet test Demo.Tests/Demo.Tests.csproj --configuration Release - dotnet publish Demo --configuration Release --runtime ubuntu.16.04-x64 --output publish/backend-conf tags: [ 2gis, docker ] artifacts: paths: - publish/backend-conf/ 50
  51. 51. build:backend-conf-demo: image: $REGISTRY/microsoft/aspnetcore-build:1.1.2 stage: build:app script: - dotnet restore --runtime ubuntu.16.04-x64 - dotnet test Demo.Tests/Demo.Tests.csproj --configuration Release - dotnet publish Demo --configuration Release --runtime ubuntu.16.04-x64 --output publish/backend-conf tags: [ 2gis, docker ] artifacts: paths: - publish/backend-conf/ 51
  52. 52. build:backend-conf-demo-image: stage: build:app script: - IMAGE=my-namespace/backend-conf TAG=$CI_TAG DOCKER_FILE=publish/backend-conf/Dockerfile DOCKER_BUILD_CONTEXT=publish/backend-conf make docker-build - IMAGE=my-namespace/backend-conf TAG=$CI_TAG make docker-push tags: [ docker-engine, io ] dependencies: - build:app 52
  53. 53. build:backend-conf-demo-image: stage: build:app script: - IMAGE=my-namespace/backend-conf TAG=$CI_TAG DOCKER_FILE=publish/backend-conf/Dockerfile DOCKER_BUILD_CONTEXT=publish/backend-conf make docker-build - IMAGE=my-namespace/backend-conf TAG=$CI_TAG make docker-push tags: [ docker-engine, io ] dependencies: - build:app 53
  54. 54. build:backend-conf-demo-image: stage: build:app script: - IMAGE=my-namespace/backend-conf TAG=$CI_TAG DOCKER_FILE=publish/backend-conf/Dockerfile DOCKER_BUILD_CONTEXT=publish/backend-conf make docker-build - IMAGE=my-namespace/backend-conf TAG=$CI_TAG make docker-push tags: [ docker-engine, io ] dependencies: - build:app 54
  55. 55. build:backend-conf-demo-image: stage: build:app script: - IMAGE=my-namespace/backend-conf TAG=$CI_TAG DOCKER_FILE=publish/backend-conf/Dockerfile DOCKER_BUILD_CONTEXT=publish/backend-conf make docker-build - IMAGE=my-namespace/backend-conf TAG=$CI_TAG make docker-push tags: [ docker-engine, io ] dependencies: - build:app 55
  56. 56. -Коротко о сервисе -On-premise платформа -.NET Core, ASP.NET Core, базовые фичи -Билд -Деплой -Нагрузочное тестирование -Performance •Кэширование •Асинхронность и многопоточность 56
  57. 57. Kubernetes 57
  58. 58. Kubernetes 58
  59. 59. Kubernetes 59
  60. 60. Kubernetes 60
  61. 61. Kubernetes 61
  62. 62. 62
  63. 63. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ app_name }} spec: replicas: {{ replicas_count }} template: metadata: labels: app: {{ app_name }} spec: containers: - name: backend-conf image: {{ image_path }}:{{ image_version }} ports: - containerPort: {{ app_port }} readinessProbe: httpGet: { path: '{{ app_probe_path }}', port: {{ app_port }} } initialDelaySeconds: 10 periodSeconds: 10 env: - name: ASPNETCORE_ENVIRONMENT value: {{ env }} 63
  64. 64. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ app_name }} spec: replicas: {{ replicas_count }} template: metadata: labels: app: {{ app_name }} spec: containers: - name: backend-conf image: {{ image_path }}:{{ image_version }} ports: - containerPort: {{ app_port }} readinessProbe: httpGet: { path: '{{ app_probe_path }}', port: {{ app_port }} } initialDelaySeconds: 10 periodSeconds: 10 env: - name: ASPNETCORE_ENVIRONMENT value: {{ env }} 64
  65. 65. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ app_name }} spec: replicas: {{ replicas_count }} template: metadata: labels: app: {{ app_name }} spec: containers: - name: backend-conf image: {{ image_path }}:{{ image_version }} ports: - containerPort: {{ app_port }} readinessProbe: httpGet: { path: '{{ app_probe_path }}', port: {{ app_port }} } initialDelaySeconds: 10 periodSeconds: 10 env: - name: ASPNETCORE_ENVIRONMENT value: {{ env }} 65
  66. 66. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ app_name }} spec: replicas: {{ replicas_count }} template: metadata: labels: app: {{ app_name }} spec: containers: - name: backend-conf image: {{ image_path }}:{{ image_version }} ports: - containerPort: {{ app_port }} readinessProbe: httpGet: { path: '{{ app_probe_path }}', port: {{ app_port }} } initialDelaySeconds: 10 periodSeconds: 10 env: - name: ASPNETCORE_ENVIRONMENT value: {{ env }} 66
  67. 67. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ app_name }} spec: replicas: {{ replicas_count }} template: metadata: labels: app: {{ app_name }} spec: containers: - name: backend-conf image: {{ image_path }}:{{ image_version }} ports: - containerPort: {{ app_port }} readinessProbe: httpGet: { path: '{{ app_probe_path }}', port: {{ app_port }} } initialDelaySeconds: 10 periodSeconds: 10 env: - name: ASPNETCORE_ENVIRONMENT value: {{ env }} 67
  68. 68. apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ app_name }} spec: replicas: {{ replicas_count }} template: metadata: labels: app: {{ app_name }} spec: containers: - name: backend-conf image: {{ image_path }}:{{ image_version }} ports: - containerPort: {{ app_port }} readinessProbe: httpGet: { path: '{{ app_probe_path }}', port: {{ app_port }} } initialDelaySeconds: 10 periodSeconds: 10 env: - name: ASPNETCORE_ENVIRONMENT value: {{ env }} 68
  69. 69. apiVersion: v1 kind: Service metadata: name: {{ app_name }} annotations: router.deis.io/domains: "{{ app_name }}" router.deis.io/ssl.enforce: "{{ ssl_enforce | default('False') }}" spec: ports: - name: http port: 80 targetPort: {{ app_port }} selector: app: {{ app_name }} 69
  70. 70. apiVersion: v1 kind: Service metadata: name: {{ app_name }} annotations: router.deis.io/domains: "{{ app_name }}" router.deis.io/ssl.enforce: "{{ ssl_enforce | default('False') }}" spec: ports: - name: http port: 80 targetPort: {{ app_port }} selector: app: {{ app_name }} 70
  71. 71. apiVersion: v1 kind: Service metadata: name: {{ app_name }} annotations: router.deis.io/domains: "{{ app_name }}" router.deis.io/ssl.enforce: "{{ ssl_enforce | default('False') }}" spec: ports: - name: http port: 80 targetPort: {{ app_port }} selector: app: {{ app_name }} 71
  72. 72. common: replicas_count: 1 max_unavailable: 0 k8s_master_uri: https://master.staging.dc-nsk1.hw:6443 k8s_token: "{{ env='K8S_TOKEN_STAGE' }}" k8s_ca_base64: "{{ env='K8S_CA' }}" k8s_namespace: my-namespace ssl_enforce: true app_port: 5000 app_probe_path: /healthcheck image_version: "{{ env='CI_TAG' }}" image_path: docker-hub.2gis.ru/my-namespace/backend-conf env: Stage backend-conf-demo: app_name: “backend-conf-demo" app_limits_cpu: 500m app_requests_cpu: 100m app_limits_memory: 800Mi app_requests_memory: 300Mi kubectl: - template: deployment.yaml.j2 - template: service-stage.yaml.j2 72
  73. 73. common: replicas_count: 1 max_unavailable: 0 k8s_master_uri: https://master.staging.dc-nsk1.hw:6443 k8s_token: "{{ env='K8S_TOKEN_STAGE' }}" k8s_ca_base64: "{{ env='K8S_CA' }}" k8s_namespace: my-namespace ssl_enforce: true app_port: 5000 app_probe_path: /healthcheck image_version: "{{ env='CI_TAG' }}" image_path: docker-hub.2gis.ru/my-namespace/backend-conf env: Stage backend-conf-demo: app_name: “backend-conf-demo" app_limits_cpu: 500m app_requests_cpu: 100m app_limits_memory: 800Mi app_requests_memory: 300Mi kubectl: - template: deployment.yaml.j2 - template: service-stage.yaml.j2 73
  74. 74. common: replicas_count: 1 max_unavailable: 0 k8s_master_uri: https://master.staging.dc-nsk1.hw:6443 k8s_token: "{{ env='K8S_TOKEN_STAGE' }}" k8s_ca_base64: "{{ env='K8S_CA' }}" k8s_namespace: my-namespace ssl_enforce: true app_port: 5000 app_probe_path: /healthcheck image_version: "{{ env='CI_TAG' }}" image_path: docker-hub.2gis.ru/my-namespace/backend-conf env: Stage backend-conf-demo: app_name: “backend-conf-demo" app_limits_cpu: 500m app_requests_cpu: 100m app_limits_memory: 800Mi app_requests_memory: 300Mi kubectl: - template: deployment.yaml.j2 - template: service-stage.yaml.j2 74
  75. 75. common: replicas_count: 1 max_unavailable: 0 k8s_master_uri: https://master.staging.dc-nsk1.hw:6443 k8s_token: "{{ env='K8S_TOKEN_STAGE' }}" k8s_ca_base64: "{{ env='K8S_CA' }}" k8s_namespace: my-namespace ssl_enforce: true app_port: 5000 app_probe_path: /healthcheck image_version: "{{ env='CI_TAG' }}" image_path: docker-hub.2gis.ru/my-namespace/backend-conf env: Stage backend-conf-demo: app_name: “backend-conf-demo" app_limits_cpu: 500m app_requests_cpu: 100m app_limits_memory: 800Mi app_requests_memory: 300Mi kubectl: - template: deployment.yaml.j2 - template: service-stage.yaml.j2 75
  76. 76. deploy:backend-conf-demo-stage: stage: deploy:stage when: manual image: $REGISTRY/2gis-io/k8s-handle:latest script: - export ENVIRONMENT=Stage - k8s-handle deploy --config config-stage.yaml --section backend-conf --sync-mode True only: - tags tags: [ 2gis, docker ] 76
  77. 77. deploy:backend-conf-demo-stage: stage: deploy:stage when: manual image: $REGISTRY/2gis-io/k8s-handle:latest script: - export ENVIRONMENT=Stage - k8s-handle deploy --config config-stage.yaml --section backend-conf --sync-mode True only: - tags tags: [ 2gis, docker ] 77
  78. 78. -Коротко о сервисе -On-premise платформа -.NET Core, ASP.NET Core, базовые фичи -Билд -Деплой -Нагрузочное тестирование -Performance •Кэширование •Асинхронность и многопоточность 78
  79. 79. Нагрузочный контур 79
  80. 80. 80
  81. 81. 81
  82. 82. 82
  83. 83. 83
  84. 84. .perf:template: &perf_template stage: test:perf environment: perf only: - master - /^perf.*$/ variables: PERF_TEST_PATH: "tests/perf" PERF_ARTIFACTS: "target/gatling" PERF_GRAPHITE_HOST: "graphite-exporter.perf.os-n3.hw" PERF_GRAPHITE_ROOT_PATH_PREFIX: "gatling.service-prefix" image: $REGISTRY/perf/tools:1 artifacts: name: perf-reports when: always expire_in: 7 day paths: - ${PERF_TEST_PATH}/${PERF_ARTIFACTS}/* tags: [perf-n3-1] 84
  85. 85. perf:run-tests: <<: *perf_template script: - export PERF_GRAPHITE_ROOT_PATH_PREFIX PERF_GRAPHITE_HOST - export PERF_APP_HOST=http://${APP_PERF}.web- staging.2gis.ru - cd ${PERF_TEST_PATH} - ./run_test.sh --capacity - ./run_test.sh --resp_time after_script: - perfberry-cli logs upload --dir ${PERF_TEST_PATH}/${PERF_ARTIFACTS} --env ${APP_PERF}.web- staging.2gis.ru gatling ${PERFBERRY_PROJECT_ID} 85
  86. 86. perf:run-tests: <<: *perf_template script: - export PERF_GRAPHITE_ROOT_PATH_PREFIX PERF_GRAPHITE_HOST - export PERF_APP_HOST=http://${APP_PERF}.web- staging.2gis.ru - cd ${PERF_TEST_PATH} - ./run_test.sh --capacity - ./run_test.sh --resp_time after_script: - perfberry-cli logs upload --dir ${PERF_TEST_PATH}/${PERF_ARTIFACTS} --env ${APP_PERF}.web- staging.2gis.ru gatling ${PERFBERRY_PROJECT_ID} 86
  87. 87. perf:run-tests: <<: *perf_template script: - export PERF_GRAPHITE_ROOT_PATH_PREFIX PERF_GRAPHITE_HOST - export PERF_APP_HOST=http://${APP_PERF}.web- staging.2gis.ru - cd ${PERF_TEST_PATH} - ./run_test.sh --capacity - ./run_test.sh --resp_time after_script: - perfberry-cli logs upload --dir ${PERF_TEST_PATH}/${PERF_ARTIFACTS} --env ${APP_PERF}.web- staging.2gis.ru gatling ${PERFBERRY_PROJECT_ID} 87
  88. 88. -Коротко о сервисе -On-premise платформа -.NET Core, ASP.NET Core, базовые фичи -Билд -Деплой -Нагрузочное тестирование -Performance •Кэширование •Асинхронность и многопоточность 88
  89. 89. Кеширование 89 [AllowAnonymous] [HttpGet("{id}")] [ResponseCache( VaryByQueryKeys = new[] { "api-version" }, Duration = 3600)] public async Task<IActionResult> Get(long id) { … }
  90. 90. Кеширование -На клиенте •Cache-Control header (HTTP 1.1 Caching) 90 [ResponseCache( VaryByQueryKeys = new[] { "api-version" }, Duration = 3600)]
  91. 91. Кеширование -На клиенте •Cache-Control header (HTTP 1.1 Caching) -На сервере •Response Caching Middleware (docs) 91 [ResponseCache( VaryByQueryKeys = new[] { "api-version" }, Duration = 3600)]
  92. 92. Performance 92 Асинхронность Многопоточность
  93. 93. Performance 93 Thread1 Task1 Асинхронность Task2Task1 Thread1 Task1 Многопоточность Task2 Thread2 Task3Task4
  94. 94. 94 var data = await _remoteService.IOBoundOperationAsync(timeoutInSec: 1); var result = new List<string>[data.Count]; foreach (var item in data) { var detailed = await _remoteService.IOBoundOperationAsync(timeoutInSec: 5); result.Add(string.Join(", ", detailed)); }
  95. 95. 95 var data = await _remoteService.IOBoundOperationAsync(timeoutInSec: 1); var result = new List<string>[data.Count]; foreach (var item in data) { var detailed = await _remoteService.IOBoundOperationAsync(timeoutInSec: 5); result.Add(string.Join(", ", detailed)); }
  96. 96. 96 var data = await _remoteService.IOBoundOperationAsync(timeoutInSec: 1); var result = new string[data.Count]; var tasks = data.Select( async (item, index) => { var detailed = await _remoteService.IOBoundOperationAsync(timeoutInSec: 5); result[index] = string.Join(", ", detailed) }); await Task.WhenAll(tasks);
  97. 97. 97 var data = await _remoteService.IOBoundOperationAsync(timeoutInSec: 1); var result = new string[data.Count]; var tasks = data.Select( async (item, index) => { var detailed = await _remoteService.IOBoundOperationAsync(timeoutInSec: 5); result[index] = string.Join(", ", detailed) }); await Task.WhenAll(tasks);
  98. 98. 98 var data = await _remoteService.IOBoundOperationAsync(timeoutInSec: 1); var result = new string[data.Count]; var tasks = data.Select( async (item, index) => { var detailed = await _remoteService.IOBoundOperationAsync(timeoutInSec: 5); result[index] = string.Join(", ", detailed) }); await Task.WhenAll(tasks);
  99. 99. Нагрузочное тестирование -Лимиты по памяти и процессору •384Mb и 1,5 CPU 99
  100. 100. Нагрузочное тестирование -Лимиты по памяти и процессору •384Mb и 1,5 CPU -Синхронный (capacity) тест •~24 RPS (без кэша) 100
  101. 101. Нагрузочное тестирование -Лимиты по памяти и процессору •384Mb и 1,5 CPU -Синхронный (capacity) тест •~24 RPS (без кэша) •~400 RPS (включен серверный кэш) 101
  102. 102. Нагрузочное тестирование -Лимиты по памяти и процессору •384Mb и 1,5 CPU -Синхронный (capacity) тест •~24 RPS (без кэша) •~400 RPS (включен серверный кэш) -Асинхронный (load) тест •Прошёл 102
  103. 103. Вместо заключения 103
  104. 104. Вместо заключения -Не бойтесь использовать .NET Core в продакшене 104
  105. 105. Вместо заключения -Не бойтесь использовать .NET Core в продакшене -Не бойтесь использовать Linux и .NET Core 105
  106. 106. Вместо заключения -Не бойтесь использовать .NET Core в продакшене -Не бойтесь использовать Linux и .NET Core -Docker и Kubernetes сильно упрощают жизнь 106
  107. 107. Вместо заключения -Не бойтесь использовать .NET Core в продакшене -Не бойтесь использовать Linux и .NET Core -Docker и Kubernetes сильно упрощают жизнь -Оптимизируйте приложения 107
  108. 108. Вместо заключения -Не бойтесь использовать .NET Core в продакшене -Не бойтесь использовать Linux и .NET Core -Docker и Kubernetes сильно упрощают жизнь -Оптимизируйте приложения •Многое есть из коробки •Думать все равно надо 108
  109. 109. Спасибо! https://github.com/denisivan0v/backend-conf-2017 109
  110. 110. Вопросы? Денис Иванов @denisivanov denis@ivanovdenis.ru https://github.com/denisivan0v 110
  111. 111. Эксплуатация 111
  112. 112. Эксплуатация -Prometheus server (/metrics) 112

×