tnblog
首页
视频
资源
登录

Kubernetes .Net6 Webhook

9915人阅读 2022/7/28 10:49 总访问:3467388 评论:0 收藏:0 手机
分类: 容器编排

Kubernetes

Kubernetes .Net6 Webhook


本文主要是学习陈计节大佬讲的 使用 .NET Core 开发 Kubernetes 基础组件记录的笔记。

Admission Webhook的介绍


Admission Webhook 是 api-server 对外提供的一个扩展能力,把K8s中的ApiServer当作我们.net的网站的话,它就像是其中的Filter过滤器一样可以拦截并修改这些请求信息。

MutatingAdmissionWebhook 与 ValidatingAdmissionWebhook


我们可以通过MutatingAdmissionWebhook 与 ValidatingAdmissionWebhook控制器进行强制实施自定义的策略。
流程如下图所示:


MutatingAdmissionWebhook:进行变更处理
ValidatingAdmissionWebhook:负责验证处理

先决条件


确保启用了MutatingAdmissionWebhookValidatingAdmissionWebhook资源类型,以及admissionregistration.k8s.io/v1API。
可通过如下命令进行检查。

  1. kubectl api-resources | egrep "ValidatingWebhookConfiguration|MutatingWebhookConfiguration"

需求分析


我们想实现如下的一个需求:
1.我们不允许来源于abcd.com网站的镜像源进行创建容器。(ValidatingAdmissionWebhook)
2.我们对创建Podannotations:k8s.jijiechen.com/inject-dotnet-helper: "true"的进行创建一个边车容器,像dapr、istio注入一样。

创建K8sWebhook项目


创建一个名为K8sWebhook API项目(添加Docker支持)。
并添加KubernetesClient.Models依赖包。

  1. <PackageReference Include="KubernetesClient.Models" Version="8.0.12" />


然后我们就需要去找到K8s的OpenAPI了,我提供了如下连接请根据k8s的版本需要对应swagger.json。

  1. https://github.com/kubernetes/kubernetes/blob/master/api/openapi-spec/swagger.json
  2. https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.20/api/openapi-spec/swagger.json


下载下来后,将json文件放入项目中的AdminReviewModels目录下。


然后我们进行添加服务。


点击完成后,我们需要对生成的类做一些修改,我们找到项目目录的obj下找到admission.swaggerClient.cs文件。
在该目录下打开一个bash窗口,并执行如下命令。
该命令可以将项目中的序列化类从Newtonsoft.Json更改为System.Text.Json

  1. sed -i 's/public RawExtension/public System.Text.Json.JsonElement/g' ./admission.swaggerClient.cs
  2. sed -i 's/\[Newtonsoft.Json.JsonProperty/[System.Text.Json.Serialization.JsonPropertyName/g' ./admission.swaggerClient.cs
  3. sed -i 's/,\ Required.*/)]/g' ./admission.swaggerClient.cs


更改完成后,我们项目创建算是完成了。

编写MutatingAdmissionWebhook控制器


创建WebhookMutate.cs文件,编写如下代码:

  1. public static class WebhookMutate
  2. {
  3. const string InjectAnnotationKeySetting = "k8s.jijiechen.com/inject-dotnet-helper";
  4. public static AdmissionReview InjectDotnetHelper(AdmissionReview reviewRequest)
  5. {
  6. // 创建通行结果
  7. var allowedResponse = CreateInjectResponse(reviewRequest, true);
  8. // 判断请求的资源类型是不是Pod,是不是创建的请求,如果不是直接放行
  9. if (reviewRequest.Request.Kind.Kind != "Pod" || reviewRequest.Request.Operation != "CREATE")
  10. {
  11. return allowedResponse;
  12. }
  13. // 序列化成一个Pod资源对象
  14. var podJson = reviewRequest.Request.Object.GetRawText();
  15. Console.WriteLine($"原来的 Pod Json:{podJson}");
  16. var pod = JsonSerializer.Deserialize<V1Pod>(podJson,
  17. new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
  18. Console.WriteLine($"序列化后 Pod Json:{JsonSerializer.Serialize(pod)}");
  19. // 判断是否需要注入
  20. var _DisabledByAnnotation = DisabledByAnnotation(pod!.Metadata.Annotations);
  21. Console.WriteLine($"判断是否需要注入 :{!_DisabledByAnnotation}");
  22. if (_DisabledByAnnotation)
  23. {
  24. return allowedResponse;
  25. }
  26. // 如果该容器是我们注入的边车容器则跳过直接放行
  27. var _AlreadyInjected = AlreadyInjected(pod);
  28. Console.WriteLine($"判断是否已经注入 :{_AlreadyInjected}");
  29. if (_AlreadyInjected)
  30. {
  31. return allowedResponse;
  32. }
  33. // 创建我们的边车容器
  34. var container = new V1Container("dotnet-helper")
  35. {
  36. Args = new[] { "infinity" },
  37. Command = new[] { "sleep" },
  38. Image = "mcr.microsoft.com/dotnet/runtime:6.0",
  39. ImagePullPolicy = "IfNotPresent"
  40. };
  41. // 获取pod名称
  42. var podName = string.IsNullOrEmpty(reviewRequest.Request.Name)
  43. ? pod.Metadata.GenerateName + "?"
  44. : reviewRequest.Request.Name;
  45. // 获取完整的pod名称
  46. var fullPodName = $"{reviewRequest.Request.Namespace}/{podName}";
  47. Console.WriteLine($"正在向此 Pod 中注入 dotnet helper:{fullPodName}");
  48. // 发起添加的请求
  49. var patch = new
  50. {
  51. op = "add",
  52. path = "/spec/containers/-",
  53. value = container
  54. };
  55. var patches = new[] { patch };
  56. var patchResponse = new AdmissionResponse()
  57. {
  58. Allowed = true,
  59. PatchType = "JSONPatch",
  60. Patch = Encoding.Default.GetBytes(JsonSerializer.Serialize(patches))
  61. };
  62. var reviewResponse = CreateInjectResponse(reviewRequest, true);
  63. // 编写请求的结果
  64. reviewResponse.Response = patchResponse;
  65. Console.WriteLine($"获取请求结果 :{JsonSerializer.Serialize(patchResponse)}");
  66. return reviewResponse;
  67. }
  68. private static bool AlreadyInjected(V1Pod pod)
  69. {
  70. return pod.Spec.Containers.Any(c => c.Name == "dotnet-helper");
  71. }
  72. /// <summary>
  73. /// 判断是否需要注入
  74. ///
  75. /// annotation: k8s.jijiechen.com/inject-dotnet-helper:true 可以通过
  76. /// annotation: k8s.jijiechen.com/inject-dotnet-helper:false 不可以通过
  77. /// </summary>
  78. /// <param name="annotations"></param>
  79. /// <returns></returns>
  80. private static bool DisabledByAnnotation(IDictionary<string, string>? annotations)
  81. {
  82. // 当创建时没有annotations我们便不进行注入
  83. if (annotations == null)
  84. {
  85. return true;
  86. }
  87. var falseValues = new[] { "no", "false", "0" };
  88. // 判断为false我们也不进行注入
  89. return annotations.TryGetValue(InjectAnnotationKeySetting, out var annotation) &&
  90. falseValues.Contains(annotation.ToLower());
  91. }
  92. static AdmissionReview CreateInjectResponse(AdmissionReview originalRequest, bool allowed)
  93. {
  94. var res = new AdmissionResponse
  95. {
  96. Allowed = allowed,
  97. Status = new Status
  98. {
  99. Code = (int)HttpStatusCode.OK,
  100. Message = string.Empty
  101. }
  102. };
  103. res.Uid = originalRequest.Request.Uid;
  104. return new AdmissionReview
  105. {
  106. ApiVersion = originalRequest.ApiVersion,
  107. Kind = originalRequest.Kind,
  108. Response = res
  109. };
  110. }
  111. }

编写ValidatingAdmissionWebhook控制器


创建WebhookValidate.cs文件,编写如下代码:

  1. public static class WebhookValidate
  2. {
  3. private static string[]? _blockedRepos = null;
  4. public static AdmissionReview CheckImage(AdmissionReview reviewRequest)
  5. {
  6. if (_blockedRepos == null)
  7. {
  8. // 获取BLOCKED_REPOS环境变量值,拒接这些网址的容器
  9. _blockedRepos = (Environment.GetEnvironmentVariable("BLOCKED_REPOS") ?? "").Split(",");
  10. }
  11. var allowed = true;
  12. // 判断请求的资源类型是不是Pod,是不是创建的请求,如果不是直接放行
  13. if (reviewRequest.Request.Kind.Kind != "Pod" || reviewRequest.Request.Operation != "CREATE")
  14. {
  15. return CreateReviewResponse(reviewRequest, allowed);
  16. }
  17. // 序列化成一个Pod资源对象
  18. var pod = JsonSerializer.Deserialize<V1Pod>(reviewRequest.Request.Object.GetRawText(),
  19. new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
  20. // 判断序列化是否成功,如果序列化失败直接放行
  21. if (pod == null)
  22. {
  23. return CreateReviewResponse(reviewRequest, allowed);
  24. }
  25. // 获取请求的Pod名称
  26. var podName = string.IsNullOrEmpty(reviewRequest.Request.Name) ? pod.Metadata.GenerateName + "?" : reviewRequest.Request.Name;
  27. // 完整的pod名称
  28. var fullPodName = $"{reviewRequest.Request.Namespace}/{podName}";
  29. // 获取初始化容器与运行容器的镜像
  30. var usedImages = new List<string>()
  31. .Concat(pod.Spec.Containers.NotEmpty().Select(c => c.Image))
  32. .Concat(pod.Spec.InitContainers.NotEmpty().Select(c => c.Image))
  33. .Distinct()
  34. .ToList();
  35. var blockedImages = new List<string>();
  36. // 拦截不允许的镜像地址
  37. var blockedCount = usedImages.Select(img =>
  38. {
  39. var shouldBlock = ImageBlocked(img);
  40. if (shouldBlock)
  41. {
  42. blockedImages.Add(img);
  43. Console.WriteLine($"Pod {fullPodName} 被拦截,因为使用被禁止的镜像 {img}");
  44. }
  45. return shouldBlock;
  46. }).Count(b => b);
  47. // 判断是否有不可以通行的镜像
  48. allowed = blockedCount == 0;
  49. // 创建放行结果
  50. var res = CreateReviewResponse(reviewRequest, allowed);
  51. // 判断剩余的通行镜像
  52. if (!allowed)
  53. {
  54. // 如果这些镜像不允许,将会进行如下操作
  55. var imageList = string.Join(",", blockedImages);
  56. res.Response.Status.Message = $"dotnet webhook: Pod should not use these images: {imageList}";
  57. }
  58. return res;
  59. }
  60. /// <summary>
  61. /// 判断不允许使用的镜像地址
  62. /// </summary>
  63. /// <param name="imageLocation"></param>
  64. /// <returns></returns>
  65. static bool ImageBlocked(string imageLocation)
  66. {
  67. if (_blockedRepos!.Length == 1 && string.IsNullOrWhiteSpace(_blockedRepos[0]))
  68. {
  69. return false;
  70. }
  71. if (!imageLocation.Contains('/'))
  72. {
  73. imageLocation = "docker.io/" + imageLocation;
  74. }
  75. return _blockedRepos
  76. .Select(r => r.EndsWith('/') ? r : string.Concat(r, '/'))
  77. .Any(r => imageLocation.StartsWith(r));
  78. }
  79. /// <summary>
  80. /// 放行
  81. /// </summary>
  82. /// <param name="originalRequest"></param>
  83. /// <param name="allowed"></param>
  84. /// <returns></returns>
  85. static AdmissionReview CreateReviewResponse(AdmissionReview originalRequest, bool allowed)
  86. {
  87. var res = new AdmissionResponse
  88. {
  89. Allowed = allowed,
  90. Status = new Status
  91. {
  92. Code = (int)HttpStatusCode.OK,
  93. Message = string.Empty
  94. }
  95. };
  96. res.Uid = originalRequest.Request.Uid;
  97. return new AdmissionReview
  98. {
  99. ApiVersion = originalRequest.ApiVersion,
  100. Kind = originalRequest.Kind,
  101. Response = res
  102. };
  103. }
  104. }


关于放行输出的格式可以参考如下文档:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/webhook/

编写Program.cs


我们设置路由/validate将进行验证处理,/mutate则进行变更处理。

  1. using K8sWebhook;
  2. var builder = WebApplication.CreateBuilder(args);
  3. builder.Services.AddControllers();
  4. builder.Services.AddEndpointsApiExplorer();
  5. builder.Services.AddSwaggerGen();
  6. var app = builder.Build();
  7. app.UseSwagger();
  8. app.UseSwaggerUI();
  9. app.MapGet("/", () => "Hello .NET Kubernetes Webhook!");
  10. app.MapPost("/validate", (AdmissionReview review) => Task.FromResult(WebhookValidate.CheckImage(review)));
  11. app.MapPost("/mutate", (AdmissionReview review) => Task.FromResult(WebhookMutate.InjectDotnetHelper(review)));
  12. app.Run();

打包


Dockerfile如下:

  1. FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
  2. WORKDIR /app
  3. EXPOSE 80
  4. FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
  5. WORKDIR /src
  6. COPY ["K8sWebhook/K8sWebhook.csproj", "K8sWebhook/"]
  7. RUN dotnet restore "K8sWebhook/K8sWebhook.csproj"
  8. COPY . .
  9. WORKDIR "/src/K8sWebhook"
  10. RUN dotnet build "K8sWebhook.csproj" -c Release -o /app/build
  11. FROM build AS publish
  12. RUN dotnet publish "K8sWebhook.csproj" -c Release -o /app/publish
  13. FROM base AS final
  14. WORKDIR /app
  15. COPY --from=publish /app/publish .
  16. ENTRYPOINT ["dotnet", "K8sWebhook.dll"]


当然也可以使用陈计节大佬编写的高山发布。

  1. FROM mcr.microsoft.com/dotnet/sdk:7.0.100-preview.4-alpine3.15-amd64 as Builder
  2. WORKDIR /src/
  3. COPY *.csproj /src/
  4. RUN dotnet restore
  5. COPY . /src/
  6. RUN mkdir /app && dotnet publish -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-musl-x64 --self-contained -o /app
  7. FROM alpine:3.15
  8. RUN apk add bash icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib
  9. WORKDIR /app
  10. COPY --from=Builder /app/* ./
  11. ENTRYPOINT ["/app/K8sWebhook"]

需要注意的是:我们打包的时候,由于openapi生成的代码在obj文件夹下面,所以我们需要将.dockerignore文件中的obj过滤给注释掉。

  1. docker build -t aidasi/webhook:v1 -f Dockerfile ..
  2. docker push aidasi/webhook:v1

编写部署文件


编写deploy.yaml部署文件。

  1. ---
  2. apiVersion: v1
  3. kind: Namespace
  4. metadata:
  5. name: kncs-system
  6. ---
  7. apiVersion: v1
  8. kind: Secret
  9. metadata:
  10. name: kncs-server-certs
  11. namespace: kncs-system
  12. type: kubernetes.io/tls
  13. data:
  14. tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUV6VENDQXJXZ0F3SUJBZ0lKQU4wTjdiTGtxM1hSTUEwR0NTcUdTSWIzRFFFQkN3VUFNRzh4Q3pBSkJnTlYKQkFZVEFrTk9NUkF3RGdZRFZRUUlEQWRDWldscWFXNW5NUkV3RHdZRFZRUUhEQWhEYUdGdmVXRnVaekVQTUEwRwpBMVVFQ2d3R1JHVjJUM0J6TVF3d0NnWURWUVFMREFOUVMwa3hIREFhQmdOVkJBTU1FMFJsZGs5d2N5QkhaVzVsCmNtbGpJRkp2YjNRd0hoY05Nakl3TlRFMU1EZzBNRE15V2hjTk1qTXdOVEUxTURnME1ETXlXakI0TVFzd0NRWUQKVlFRR0V3SkRUakVSTUE4R0ExVUVDQXdJVTJobGJucG9aVzR4RURBT0JnTlZCQWNNQjA1aGJuTm9ZVzR4RHpBTgpCZ05WQkFvTUJrUmxkazl3Y3pFTU1Bb0dBMVVFQ3d3RFVFdEpNU1V3SXdZRFZRUUREQnhyYm1OekxYZGxZbWh2CmIyc3VhMjVqY3kxemVYTjBaVzB1YzNaak1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXV5aTJwdGM3NWVxVG5BWDRHQnZmUXpqOGt3SWVQcVRIU3hVOWtpWFpKL1JMUWdheHBUUmROL3F0SkNFVwpaSUhOUHFJVU5wNnRpeXF4d3poQzR5UHNkS3JIREl4eGQreHFPMEp3VmNMNUk4dEhsT0RIY0pJUDM2L1ZKS0VBClNlT2lqMm91T3pkYXBZRFh2dm5wVkdMQzRkKzNiTVM5cXNVMjFUS2VqU1VNZGZhdVdObXdzNGk1eVRlVVA1WUkKeWdBZWRYRGs5ZWM2WmtUNTBFV2JhU1VQdkNEWEZHY3NnZVQxSlNkclgxemw5dXl3bjFZa1h2d3gyd0VYdTlHUApqdVRMYmxGSEkzYnRKTTJVNldkbXJvTWlESmY4V1dPeXNCdDMvczVjZWduRTUrSDgxVzV0ZWR4WWc3MUMvUzVmCjgrWFpsbG9GL3FteVhwZnhEM0x6QUtFbGV3SURBUUFCbzJNd1lUQUpCZ05WSFJNRUFqQUFNQXNHQTFVZER3UUUKQXdJRjREQkhCZ05WSFJFRVFEQStnaHhyYm1OekxYZGxZbWh2YjJzdWEyNWpjeTF6ZVhOMFpXMHVjM1pqZ2g0cQpMbXR1WTNNdGQyVmlhRzl2YXk1cmJtTnpMWE41YzNSbGJTNXpkbU13RFFZSktvWklodmNOQVFFTEJRQURnZ0lCCkFKTmlEbGp0WEM4RldPT0dsMmdnODg3RExLTmROQ3NuYzhRRUR3NXNJWUhUN2FkSFAzQlRPSG0xdGN6dW5uL2sKWEVTVEd1MzQwZmhpanljeXViQjJsaCsvYXBKZDNPQkNwdkNjLzg0c3l5Z3lnenFUWGxKUmowUzBiaWhWQ2xLNwpnZHVSYWpxQnpBWE55RkcrM25LWkpEQVk3NklMNlVFejdzWXlFSTdsMlFtQmN4VTM5VmJuU0dnMCt1czZhWWtnCjF6WXA3c3NUcEpMS013cHZYOG96TnY1TjFKOGFHVjJJYldUdHh2cUR1N01xZ2FHbzBlZUl2K3NwNVRSRiswWUoKYXBXS1Bzd2wxMW1hMEpPS1Z5RG8zOEUrT3Q1K2FFdjF6V3BEaGU0NHpaOVB4U01uaGVTMTBySHRFdkZ4bmxpWApGeWlwVkJBK2lFWUQ2b0ZVeWIxVzJUN2Zna1M2UWdrSzVQeGRITFg3U2VqaHh3OTRzbzllWjBqSGZLd012WWNwCktHenhnOUJYMnpKSmcwM0sramUwemhIenkzMy9KYVlzVzlXVTF1UjlqenlvMWtMWFZDZWhXNlFlbDV6bzFMKzAKYkZPVHc3bnhtTW1sdTJ6NTFUVld0OHlvUWdWVEJzRFFxZDg1MlVHMGwzYVp6ZFlLUmN6MlpwZ3VqVjRCZjlmaAp0RjFXNXZsYnlPR05wM0ZqV2NkdTNxZW5PU0RtaUd3V0dYZjNKK0o4bmUwa0dKajBabHljekxTbUdHYlo1YlBGClZIaWJISlc4YnAwMGpYNVVTVFY4blQxLzVFT3k3blRpRUJXRDZpNE1SUGl5ZmwvNHVGdGFUeTlNRm5KRDYrSnQKWGVmR3VvWXVFUDNqN1RNWmJYZnJzSFk1dDcvZGV1bldyWVh6K3JkQ25PVUEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  15. tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdXlpMnB0Yzc1ZXFUbkFYNEdCdmZRemo4a3dJZVBxVEhTeFU5a2lYWkovUkxRZ2F4CnBUUmROL3F0SkNFV1pJSE5QcUlVTnA2dGl5cXh3emhDNHlQc2RLckhESXh4ZCt4cU8wSndWY0w1STh0SGxPREgKY0pJUDM2L1ZKS0VBU2VPaWoyb3VPemRhcFlEWHZ2bnBWR0xDNGQrM2JNUzlxc1UyMVRLZWpTVU1kZmF1V05tdwpzNGk1eVRlVVA1WUl5Z0FlZFhEazllYzZaa1Q1MEVXYmFTVVB2Q0RYRkdjc2dlVDFKU2RyWDF6bDl1eXduMVlrClh2d3gyd0VYdTlHUGp1VExibEZISTNidEpNMlU2V2Rtcm9NaURKZjhXV095c0J0My9zNWNlZ25FNStIODFXNXQKZWR4WWc3MUMvUzVmOCtYWmxsb0YvcW15WHBmeEQzTHpBS0VsZXdJREFRQUJBb0lCQUQwUE1rL0tKbk9ERFRjNAp4MUR1UHUrS2R2UnJHM3pxZTA1bWxwaklta2tyclNYVVV6NkhqK1lFZFZvMUpUNFREdWZoTHVFRzhhMVdkM291Ckw3dzA2eDdBM0lHZWpDSkkwZnVWV0ZyU2FqK2dRVEUwQ0QwVW1mTXJSVWxXOFdZcHlzNHBJUDRXdUE4SXN0cE8KWkM0d3JrM01rK1g3WmJtQjc3cXNjZ2V3VDVsb0d6OGdKNG14dVF5MnR5SWF6d3VUQnBkbm5VUWh0QkJLZ2J1YwplYnpTS0luZ3RXa2o3SHlDWmVYY0xTdmpORzNGcEpWUXZZK1pncC9rbno1S042RFZXUkZNWThFV3BpTVVaVE1HCmVVVCtuSER4NkZlSVllNVBhRG41TWROTWhPOGZRYWdBVG9GcDRkRU5xb0l6RWVIWkZwOEJTODgwTXpTQSt0UTgKanNMaEt1RUNnWUVBNXYvTFptNXVaQ1RwNnY0NXJjQlRIM2VadXVmOVJ1Z01HYXJMS2tTS1RHZGZLMEg3dlVMZQpBSDlxalVuMExtSUxscVdmcHJiMC90eVBTUEFaSVhYY0FYQVA1TDJBdlJNNnQ1c0JXWk5HcnV5aVcyWkxTQzhQCnROQ3RHTkxmK0ZGQzJnZjlBTzZtZjhFRzlkYW9uNFdvKzZWU1FYMlRiOUZMYkxHWnByeDZjL0VDZ1lFQXoycEIKcnU1TE1ENS9abHpwY2xJNGg4RytpZ3dDcjM4K3V1NmdXRmZFZVRkYjk0cm1RclptM2xPQkxqVTBteExUTlUrVQp3NEVVQ3piR2xyYlNWRVZZL0s0Zi9ZZXBXazB4cjY3aUN1amd5ZzVSbDM5WFlMYXhxMVkvUzZ0TlNQdVQ2RjYzCkJMY1RYM3YrQ2RDR0Q5L0V1TWFEcTBJM0h5TjlOVkVPaCsrR2JDc0NnWUVBMk9MZ3Vnc0RrUGxydTl6NGtOL2IKNjlhaXUyK29TZFFEMEhHaEVjMkt3RlBxY2pZZ3c4R3RxVy80dmpIcWwwWXROVVBLazRDQ3BXeTNCN2VQRVBDVgpJYkJ5NjhUVnhERHkxNE10RUVxTWVoN3FEY0VNKy9oYjJkeDRPYTk4NUt4L2hURXM1cHdzTGhVeGtNNzhRZE1BCkowNUEzZ2FtMEwwRkFVZjdTU2I4SGpFQ2dZQTUrN2xxL3NEVU50U0V1RHFtcytlTHhCVFJJTFJyZlVYN0doU0gKUGRuMkRRelBzZXZYQUlqWFpENjd2VEg4bkJHaFdLTDgySXZTNnJndmorSlNucVJXMXhLb1hKRnlaaHdhd2VmOQpKc2NZbFZJbjZQaHpWLzlwSjQ1QVNCNHQ1ZTZlU2tRZHRGUmRJQnVQZ05USmdVUE1aK3FOS05DaUN0akkyK1VWCkNWZnB5d0tCZ0hvbERiZVRvbTZKbHppcnN0TXFlNUZwZ0NBQ0VIcDR1dkw1Tjk4TmlzWmNPRm05d1ZKcnErYVIKUVJDT1pXRnY0OTM1a2tFLzJnRDVzRnZYZzUzZmVjbmV2UFFWRytsbU1sUXRoSlR0NzRpTkZpRlBqRDR5Y0lDNQpCZG5yWkNDTWJMWHVUdEhiZmtIbXphRlExaUFKUTV0SkN3dG04TFVEd0YvMzM2bk5FN1RYCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
  16. ---
  17. apiVersion: apps/v1
  18. kind: Deployment
  19. metadata:
  20. labels:
  21. app: kncs-webhook
  22. kncs: webhook
  23. name: kncs-webhook
  24. namespace: kncs-system
  25. spec:
  26. progressDeadlineSeconds: 600
  27. replicas: 1
  28. selector:
  29. matchLabels:
  30. kncs: webhook
  31. template:
  32. metadata:
  33. annotations:
  34. sidecar.istio.io/inject: "false"
  35. k8s.jijiechen.com/inject-dotnet-helper: "false"
  36. labels:
  37. kncs: webhook
  38. spec:
  39. containers:
  40. - env:
  41. - name: BLOCKED_REPOS
  42. value: abcd.com
  43. - name: ASPNETCORE_URLS
  44. value: 'https://+:443'
  45. - name: ASPNETCORE_Kestrel__Certificates__Default__Path
  46. value: /etc/kncs/certs/tls.crt
  47. - name: ASPNETCORE_Kestrel__Certificates__Default__KeyPath
  48. value: /etc/kncs/certs/tls.key
  49. # - name: Logging__LogLevel__Default
  50. # value: Debug
  51. # - name: Logging__LogLevel__Microsoft.AspNetCore
  52. # value: Debug
  53. # - name: DEBUG
  54. # value: 'true'
  55. # - name: Microsoft__AspNetCore__HttpLogging__HttpLoggingMiddleware
  56. # value: Information
  57. image: aidasi/webhook:v1
  58. imagePullPolicy: IfNotPresent
  59. name: webhook
  60. ports:
  61. - containerPort: 443
  62. protocol: TCP
  63. resources:
  64. requests:
  65. cpu: 50m
  66. memory: 128Mi
  67. volumeMounts:
  68. - mountPath: /etc/kncs/certs
  69. name: certs
  70. restartPolicy: Always
  71. schedulerName: default-scheduler
  72. volumes:
  73. - name: certs
  74. secret:
  75. defaultMode: 420
  76. secretName: kncs-server-certs
  77. ---
  78. apiVersion: v1
  79. kind: Service
  80. metadata:
  81. labels:
  82. app: kncs-webhook
  83. name: kncs-webhook
  84. namespace: kncs-system
  85. spec:
  86. ports:
  87. - name: https
  88. port: 443
  89. protocol: TCP
  90. targetPort: 443
  91. selector:
  92. kncs: webhook
  93. sessionAffinity: None
  94. type: ClusterIP
  95. ---
  96. apiVersion: admissionregistration.k8s.io/v1
  97. kind: ValidatingWebhookConfiguration
  98. metadata:
  99. name: validating-webhook-kncs
  100. webhooks:
  101. - admissionReviewVersions:
  102. - v1beta1
  103. - v1
  104. clientConfig:
  105. caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZzVENDQTVtZ0F3SUJBZ0lKQU1LNnNwL3N3Uzl5TUEwR0NTcUdTSWIzRFFFQkN3VUFNRzh4Q3pBSkJnTlYKQkFZVEFrTk9NUkF3RGdZRFZRUUlEQWRDWldscWFXNW5NUkV3RHdZRFZRUUhEQWhEYUdGdmVXRnVaekVQTUEwRwpBMVVFQ2d3R1JHVjJUM0J6TVF3d0NnWURWUVFMREFOUVMwa3hIREFhQmdOVkJBTU1FMFJsZGs5d2N5QkhaVzVsCmNtbGpJRkp2YjNRd0hoY05Nakl3TlRFMU1EZzBNRE14V2hjTk16SXdOVEV5TURnME1ETXhXakJ2TVFzd0NRWUQKVlFRR0V3SkRUakVRTUE0R0ExVUVDQXdIUW1WcGFtbHVaekVSTUE4R0ExVUVCd3dJUTJoaGIzbGhibWN4RHpBTgpCZ05WQkFvTUJrUmxkazl3Y3pFTU1Bb0dBMVVFQ3d3RFVFdEpNUnd3R2dZRFZRUUREQk5FWlhaUGNITWdSMlZ1ClpYSnBZeUJTYjI5ME1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeFZHdUwrcnoKOGJXWWtoME5SbDUyOHJFcHlhNmVoU2tPdUU1QWN6MERGMGJCSjRyZzVCaE8xS1NyWlhzd1krVFp6c1ZqL0pKbwpWYnd5YXdxcTZRZ2tXWEhUTHcya3d3UE5MQ1RYakk4TUZ0TUlKdkdYaktJZnp6aXE1c2hxYVVWWU8vNm13WFBmCjdTMVhVOFFrelErNnJ0aEgvUFdYYkFjMWlRbXhvWGgvVXVRZkRQMHcrM3djelZwc0VYSm5xVmFnYUlBbDdSbXkKMEhYM0JoV281WTQwMFFVSEF3bnJpUUVXWmRhYUEwaExGaFZqMHl4WW5HUnBQOWJjUEZmc0poOUFyam04M2pSRQp3RU02MVFJOU81TExmMlJTTlFmMnB5ZmNKZm1KZWVvK2pVQTEwZjJWOC8wY3YvRGJVZitjZDFRWGhUQllEek9iCnlNWWZsMUFIMGIxZit0YU9WYlFmT0QwalRyWXNLS1hrYmtYNUpPZzBXMndvd3VYbURXa1A1aGl3MUZNRGN5MngKU0dsRG8wQjNQMXVCSkJzc0ErUXlvT05YWDBoYWRsekR0NDhpbk5hdTF5L1Exb1daMHJvZDFBa0t0TnJBRk1WYQpxekhVRVRtTDNaRUtXbDFwbjBadE40ZUNVSEZhOGZDZ0MwVDUvK0VFU1ZoV1dPQ2FjU1VJbkIyUFlxZnBUWnlMCmZ6UnRRR2hYcE1xcWVCbDNvcjJtUUI5REZKdHZNZWl4Y3VHTVdSTGIyOWkxKy9qVkdzVTU5ck9tTDJIaXJsTjIKU2Izcmd5RkpXb3FyQmN5L215dEJ5VG9sc1RPc1BTRGRORE81ZGRiVkVLMk9IZC9YZ29MUTh1TVpCUng4Y3Y1dQo1a0grNS9xanRGbi9rcDg0Sy9yY0k2T2V4eWFielRsQm51Y0NBd0VBQWFOUU1FNHdIUVlEVlIwT0JCWUVGQnlmCmllYnU3N3pHQ1lwbU9EU3pmWmMzUXRuQ01COEdBMVVkSXdRWU1CYUFGQnlmaWVidTc3ekdDWXBtT0RTemZaYzMKUXRuQ01Bd0dBMVVkRXdRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUNCZkpzMXNtYnZDUHYxYQorcWZQTy8vbmpPTHBOVm1ybzZZcVp6SkJqbHo0VmpVZ1JOMGxrQ1ltTk9oM0orRnVMRGNwMi9XRjJ5RnlkZlhLClBORGZQb2xkQ2RrdHh5OVk2SlpHM3dOQ3pqTEdJWVpwZStET2RjSmswellqYy9HRWFNTkVmQVVJN09zSFlDcTUKLy8ycWNQTWpOOG1TTnJiM0FwZkZaNmJsMUFGSW9NNzBZUUFDbHRKTWdkNFRSSWhISlBibmdaVjE0b1kyNWp6UApscFBMUVVRV1B2V00wbm43QW1OMldmdG5mNGl2ZzludHczN1pvdm1nc0JhaHhockRCWTBUbmdYNWFlVElHOEFICmFvZjhneUxvUVIzQjhZeURBU2N5Y0l0ZUhNdDgrNVp3YkdFTVdJanVFNUdKcUtBeWZDOUlIWjdFMHBIbUg2ck0KY1hoaVFNTjdLZm1LaGwvbGZtbmlIeEpTNnE2eUlsbmU4NDRpK0l3Y3crL0sxbUJDdFZDZ0R4OUc3TWo2QUljeApWekJyYlVrSUdQKzRxc2NmbDRGTDRIbGdjOE9vMnpYbmc1T2VpQ2JTSGRXK21zMFdXeVM1cGRZd29BbXFzaisyCnYzdmtMS3llR0lWNHBscWl4ZjMybXdrTW9NTXRPQ1NNR3FzZmhwTFFLUUhSaXNxMjNhc0tOYVZXV0RtcHFqa2YKbmZPWFJKdkpDbktFMXRJNkpPbjViN3pCc1JjaC9maVRRcmZ3Zk1sU3N2N2ZCRGZEbE8zU09STkhHTU0zRndUdgpZYjhsMllaTEpTNmpoeVZsUVZESjRUVW5TeXZJVURxOWJhZVJKTU1vZ3g2QmhTY2tGbFhwRmJiT2FVMmMzaktsCmtlUFVKZEUzU1ZnbWt6RW10YmJVTHdnOENPV0UKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  106. service:
  107. name: kncs-webhook
  108. namespace: kncs-system
  109. path: /validate
  110. port: 443
  111. failurePolicy: Fail
  112. name: image-validator.kncs.io
  113. namespaceSelector: {}
  114. objectSelector: {}
  115. rules:
  116. - apiGroups:
  117. - ""
  118. apiVersions:
  119. - v1
  120. operations:
  121. - CREATE
  122. - UPDATE
  123. - DELETE
  124. resources:
  125. - pods
  126. scope: '*'
  127. sideEffects: None
  128. timeoutSeconds: 30
  129. ---
  130. apiVersion: admissionregistration.k8s.io/v1
  131. kind: MutatingWebhookConfiguration
  132. metadata:
  133. name: mutating-webhook-kncs
  134. webhooks:
  135. - admissionReviewVersions:
  136. - v1beta1
  137. - v1
  138. clientConfig:
  139. caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZzVENDQTVtZ0F3SUJBZ0lKQU1LNnNwL3N3Uzl5TUEwR0NTcUdTSWIzRFFFQkN3VUFNRzh4Q3pBSkJnTlYKQkFZVEFrTk9NUkF3RGdZRFZRUUlEQWRDWldscWFXNW5NUkV3RHdZRFZRUUhEQWhEYUdGdmVXRnVaekVQTUEwRwpBMVVFQ2d3R1JHVjJUM0J6TVF3d0NnWURWUVFMREFOUVMwa3hIREFhQmdOVkJBTU1FMFJsZGs5d2N5QkhaVzVsCmNtbGpJRkp2YjNRd0hoY05Nakl3TlRFMU1EZzBNRE14V2hjTk16SXdOVEV5TURnME1ETXhXakJ2TVFzd0NRWUQKVlFRR0V3SkRUakVRTUE0R0ExVUVDQXdIUW1WcGFtbHVaekVSTUE4R0ExVUVCd3dJUTJoaGIzbGhibWN4RHpBTgpCZ05WQkFvTUJrUmxkazl3Y3pFTU1Bb0dBMVVFQ3d3RFVFdEpNUnd3R2dZRFZRUUREQk5FWlhaUGNITWdSMlZ1ClpYSnBZeUJTYjI5ME1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeFZHdUwrcnoKOGJXWWtoME5SbDUyOHJFcHlhNmVoU2tPdUU1QWN6MERGMGJCSjRyZzVCaE8xS1NyWlhzd1krVFp6c1ZqL0pKbwpWYnd5YXdxcTZRZ2tXWEhUTHcya3d3UE5MQ1RYakk4TUZ0TUlKdkdYaktJZnp6aXE1c2hxYVVWWU8vNm13WFBmCjdTMVhVOFFrelErNnJ0aEgvUFdYYkFjMWlRbXhvWGgvVXVRZkRQMHcrM3djelZwc0VYSm5xVmFnYUlBbDdSbXkKMEhYM0JoV281WTQwMFFVSEF3bnJpUUVXWmRhYUEwaExGaFZqMHl4WW5HUnBQOWJjUEZmc0poOUFyam04M2pSRQp3RU02MVFJOU81TExmMlJTTlFmMnB5ZmNKZm1KZWVvK2pVQTEwZjJWOC8wY3YvRGJVZitjZDFRWGhUQllEek9iCnlNWWZsMUFIMGIxZit0YU9WYlFmT0QwalRyWXNLS1hrYmtYNUpPZzBXMndvd3VYbURXa1A1aGl3MUZNRGN5MngKU0dsRG8wQjNQMXVCSkJzc0ErUXlvT05YWDBoYWRsekR0NDhpbk5hdTF5L1Exb1daMHJvZDFBa0t0TnJBRk1WYQpxekhVRVRtTDNaRUtXbDFwbjBadE40ZUNVSEZhOGZDZ0MwVDUvK0VFU1ZoV1dPQ2FjU1VJbkIyUFlxZnBUWnlMCmZ6UnRRR2hYcE1xcWVCbDNvcjJtUUI5REZKdHZNZWl4Y3VHTVdSTGIyOWkxKy9qVkdzVTU5ck9tTDJIaXJsTjIKU2Izcmd5RkpXb3FyQmN5L215dEJ5VG9sc1RPc1BTRGRORE81ZGRiVkVLMk9IZC9YZ29MUTh1TVpCUng4Y3Y1dQo1a0grNS9xanRGbi9rcDg0Sy9yY0k2T2V4eWFielRsQm51Y0NBd0VBQWFOUU1FNHdIUVlEVlIwT0JCWUVGQnlmCmllYnU3N3pHQ1lwbU9EU3pmWmMzUXRuQ01COEdBMVVkSXdRWU1CYUFGQnlmaWVidTc3ekdDWXBtT0RTemZaYzMKUXRuQ01Bd0dBMVVkRXdRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUNCZkpzMXNtYnZDUHYxYQorcWZQTy8vbmpPTHBOVm1ybzZZcVp6SkJqbHo0VmpVZ1JOMGxrQ1ltTk9oM0orRnVMRGNwMi9XRjJ5RnlkZlhLClBORGZQb2xkQ2RrdHh5OVk2SlpHM3dOQ3pqTEdJWVpwZStET2RjSmswellqYy9HRWFNTkVmQVVJN09zSFlDcTUKLy8ycWNQTWpOOG1TTnJiM0FwZkZaNmJsMUFGSW9NNzBZUUFDbHRKTWdkNFRSSWhISlBibmdaVjE0b1kyNWp6UApscFBMUVVRV1B2V00wbm43QW1OMldmdG5mNGl2ZzludHczN1pvdm1nc0JhaHhockRCWTBUbmdYNWFlVElHOEFICmFvZjhneUxvUVIzQjhZeURBU2N5Y0l0ZUhNdDgrNVp3YkdFTVdJanVFNUdKcUtBeWZDOUlIWjdFMHBIbUg2ck0KY1hoaVFNTjdLZm1LaGwvbGZtbmlIeEpTNnE2eUlsbmU4NDRpK0l3Y3crL0sxbUJDdFZDZ0R4OUc3TWo2QUljeApWekJyYlVrSUdQKzRxc2NmbDRGTDRIbGdjOE9vMnpYbmc1T2VpQ2JTSGRXK21zMFdXeVM1cGRZd29BbXFzaisyCnYzdmtMS3llR0lWNHBscWl4ZjMybXdrTW9NTXRPQ1NNR3FzZmhwTFFLUUhSaXNxMjNhc0tOYVZXV0RtcHFqa2YKbmZPWFJKdkpDbktFMXRJNkpPbjViN3pCc1JjaC9maVRRcmZ3Zk1sU3N2N2ZCRGZEbE8zU09STkhHTU0zRndUdgpZYjhsMllaTEpTNmpoeVZsUVZESjRUVW5TeXZJVURxOWJhZVJKTU1vZ3g2QmhTY2tGbFhwRmJiT2FVMmMzaktsCmtlUFVKZEUzU1ZnbWt6RW10YmJVTHdnOENPV0UKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  140. service:
  141. name: kncs-webhook
  142. namespace: kncs-system
  143. path: /mutate
  144. port: 443
  145. failurePolicy: Fail
  146. name: dotnet-helper-injector.kncs.io
  147. namespaceSelector: {}
  148. objectSelector: {}
  149. rules:
  150. - apiGroups:
  151. - ""
  152. apiVersions:
  153. - v1
  154. operations:
  155. - CREATE
  156. - UPDATE
  157. - DELETE
  158. resources:
  159. - pods
  160. scope: '*'
  161. sideEffects: None
  162. timeoutSeconds: 30


关于细节我在后面会讲到。

进行部署与测试

  1. kubectl apply -f deploy.yaml
  2. kubectl get all -n kncs-system


先测试不注入的情况。

  1. kubectl run nginx --image=nginx
  2. kubectl get pod


测试需要注入的情况,我们写道一个pod.yaml的文件下面。

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. annotations:
  5. k8s.jijiechen.com/inject-dotnet-helper: 'true'
  6. labels:
  7. app: test
  8. name: webhook-tester-mutating
  9. namespace: default
  10. spec:
  11. containers:
  12. - args:
  13. - infinity
  14. command:
  15. - sleep
  16. image: centos:7
  17. imagePullPolicy: IfNotPresent
  18. name: tester
  1. kubectl apply -f pod.yaml
  2. kubectl get pod -w


测试注入成功。
同样我们可以测试注入的日志信息。

  1. kubectl get pod -n kncs-system
  2. kubectl logs <pod名称> -n kncs-system -f

抱歉这里判断是否已经注入有点点问题,我取反了哈哈哈!我等会再上传一个你们不会出现这个问题,程序是没问题的,输出日志取反了。


接下来我们创建一个镜像源来自abcd.com看看能不能创建

  1. kubectl run aidasi --image=abcd.com/aidasi:v1


我们发现直接报错了。
接着我们来看看日志,发现有一条被禁止的镜像。


到这里我们的Webhook就算完成了,下面我们来讲讲部署时ValidatingWebhookConfigurationMutatingWebhookConfiguration所使用到的参数。

相关参数说明

字段 描述
admissionReviewVersions 设置可以解析的API服务器版本。
clientConfig 可以设置webhook的来源,它可以设置内部服务,也可以设置外部链接,我这里是设置内部提供的webhook服务。更多请参考:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/
clientConfig.caBundle 设置请求时的访问证书。
clientConfig.service 指定的提供的webhook服务。
clientConfig.service.namespace 提供服务的名称空间
clientConfig.service.name 提供服务的服务名
clientConfig.service.path 提供服务的路径
clientConfig.service.port 提供服务的端口
failurePolicy 定义了如何处理准入 webhook 中无法识别的错误和超时错误。允许的值为IgnoreFail
Ignore 表示调用 webhook 的错误将被忽略并且允许 API 请求继续。
Fail 表示调用 webhook 的错误导致准入失败并且 API 请求被拒绝。
准入 Webhook 所用的默认 failurePolicyFail
webhooks.name 资源名称
webhooks.namespaceSelector Webhook 可以根据具有名字空间的资源所处的名字空间的标签来选择拦截哪些资源的操作。
webhooks.objectSelector Webhook 能够根据可能发送的对象的标签来限制哪些请求被拦截。
webhooks.rules 匹配请求规则
webhooks.rules.operations 列出一个或多个要匹配的操作。 可以是CREATEUPDATEDELETECONNECT*以匹配所有内容。
webhooks.rules.apiGroups 列出了一个或多个要匹配的 API 组。` 是核心 API 组。*` 匹配所有 API 组。
webhooks.rules.apiVersions 列出了一个或多个要匹配的 API 版本。* 匹配所有 API 版本。
webhooks.rules.resources 列出了一个或多个要匹配的资源。
webhooks.rules.scope 指定要匹配的范围。有效值为 ClusterNamespaced*。 子资源匹配其父资源的范围。默认值为 *
webhooks.sideEffects 副作用。简单来讲创建有没有问题。
具体请参考:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#side-effects
webhooks.timeoutSeconds 用来配置在将调用视为失败之前,允许 API 服务器等待 Webhook 响应的时间长度。

欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

cAPS.net 保存base64位格式的图片

publicvoidUpload() { //取出图片对应的base64位字符 stringimgBase=Request[&quot;imgBase&quot;]; //c#里边的base6...

Quartz.net实例动态改变周期调度。misfire、Cron

Quartz:Java编写的开源的任务调度作业框架 类似Timer之类定时执行的功能,但是更强大Quartz.NET:是把Quartz转成C# NuGet...

.net Windows服务发布、安装、卸载、监听脚本。服务调试

一、脚本 为方便不用每次都去写安装卸载的脚本1.安装脚本@echooff @echo开始安装【服务】 %SystemRoot%\Microsoft.NET\Fr...

c、VB.net中全角半角转换方法

///&lt;summary&gt; ///转全角的函数(SBCcase) ///&lt;/summary&gt; ///&lt;paramname=&quot;input&quot;&gt;任意字符串...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

C.net 配合小程序实现经过第三方服务器中转文件

某些时候,微信小程序前段上传文件的时候需要经过第三方服务器再将文件上传到客户的服务器;操作如下:1:(小程序内向中端服...

.net实现QQ邮箱发送邮件功能

1、微软已经帮我们封装好了发送邮件的类MailMessage,MailMessage类构造一些邮件信息,然后通过SmtpClient进行邮件发送。Mai...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

windows 自带的netsh进行端口映射

使用netsh 把本地任意ip的25566端口 映射到192.168.81.234的25565端口netshinterfaceportproxyaddv4tov4listenaddress=0.0....

确保.net程序始终以管理员身份运行

usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; ...

ASP.net Timer细节处理

Timer的用法:1:本人称之为计时器,是asp.net官方的一种。用法即是计时所用 2:关于计时有很多中方式,本人学识有限,暂...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

asp.net主动推送百度seo

虽然可以使用百度提供的js自动推送,但是估计度娘还是希望主动推送一点。哈哈^_^,女孩子嘛大多都喜欢被动一点。publicclass...
这一世以无限游戏为使命!
排名
2
文章
634
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术