深秋暖阳 發表於 2022-5-6 08:54:00

使用 .net + blazor 做一个 kubernetes 开源文件系统

<h2>背景</h2>
<p>据我所知,目前 kubernetes 本身或者其它第三方社区都没提供 kubernetes 的文件系统。也就是说要从 kubernetes 的容器中下载或上传文件,需要先进入容器查看目录结构,然后再通过 kubectl cp 指令把文件拷贝进或出容器。虽然说不太麻烦,但也不太方便。当时正好推出 .net 5 + blazor,就趁着这个机会使用&nbsp;.net 5 + blazor 做一个 kubernetes 的开源文件系统。</p>
<p>&nbsp;</p>
<h2>界面简介</h2>
<h4>创建集群</h4>
<p>创建集群其实就是上传需要接管的 kubernetes 的 kubeconfig,并给集群取个帮助区分的名字:</p>
<p><img src="https://img2022.cnblogs.com/blog/182190/202204/182190-20220424232130342-1653087637.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>浏览、上传、下载文件</h4>
<p>创建完集群后,就可以方便地选择集群 -&gt; 命名空间 -&gt; Pod -&gt; 容器,然后浏览容器目录,上传文件到容器,或者下载文件到本地:</p>
<p><img src="https://img2022.cnblogs.com/blog/182190/202204/182190-20220424233126656-103899502.png" alt="" loading="lazy"></p>
<h2>&nbsp;</h2>
<h2>使用方法</h2>
<ol>
<li>克隆代码,https://github.com/ErikXu/kubernetes-filesystem</li>
<li>安装 docker</li>
<li>执行&nbsp;bash build.sh 指令</li>
<li>执行&nbsp;bash pack.sh 指令</li>
<li>下载&nbsp;kubectl 并保存到&nbsp;/usr/local/bin/kubectl</li>
<li>执行 bash run.sh 指令</li>
</ol>
<p>&nbsp;</p>
<h2>代码目录</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:false;">├── build.sh                                  # 构建脚本
├── docker                                    # docker 目录
│&nbsp;&nbsp; └── Dockerfile                            # Dockerfile
├── pack.sh                                 # 打包脚本
├── publish.sh                              # 发布脚本
├── README_CN.md                              # 项目说明(中文)
├── README.md                                 # 项目说明
├── run.sh                                    # 运行脚本
└── src                                       # 源码目录
    ├── Kubernetes.Filesystem.sln             # 解决方案
    ├── Web                                 # Web 项目
    │&nbsp;&nbsp; ├── App.razor                         # 入口 APP
    │&nbsp;&nbsp; ├── _Imports.razor                  # 引用文件
    │&nbsp;&nbsp; ├── Pages
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── Cluster.razor               # 集群管理页面
    │&nbsp;&nbsp; │&nbsp;&nbsp; └── File.razor                  # 文件管理页面
    │&nbsp;&nbsp; ├── Shared
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── MainLayout.razor            # 布局文件
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── MainLayout.razor.css          # 布局样式文件
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── NavMenu.razor               # 导航文件
    │&nbsp;&nbsp; │&nbsp;&nbsp; ├── NavMenu.razor.css             # 导航样式文件
    │&nbsp;&nbsp; │&nbsp;&nbsp; └── SurveyPrompt.razor            # 调查弹出框
    │&nbsp;&nbsp; ├── Web.csproj                        # Web 项目文件
    │&nbsp;&nbsp; └── wwwroot
    │&nbsp;&nbsp;   ├── css                           # 样式文件夹
    │&nbsp;&nbsp;   ├── favicon.ico                   # icon 文件
    │&nbsp;&nbsp;   └── index.html                  # html 入口页
    └── WebApi                              # WebApi 项目
      ├── appsettings.Development.json      # 开发环境配置文件
      ├── appsettings.json                  # 配置文件
      ├── Controllers                     # 控制器目录
      │&nbsp;&nbsp; ├── ClustersController.cs         # 集群控制器
      │&nbsp;&nbsp; ├── ContainersController.cs       # 容器控制器
      │&nbsp;&nbsp; ├── FilesController.cs            # 文件控制器
      │&nbsp;&nbsp; ├── NamespacesController.cs       # 命名空间控制器
      │&nbsp;&nbsp; └── PodsController.cs             # Pod 控制器
      ├── Startup.cs                        # Startup 文件
      └── WebApi.csproj                     # WebApi 项目文件</pre>
</div>
<h2>&nbsp;</h2>
<h2>代码简析</h2>
<h4><span class="pl-en">ClustersController</span></h4>
<p>ClustersController 主要对集群进行管理,集群信息使用 json 文件存储,路径为:/root/k8s-config。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_9a2ec43c-4b04-4844-a2d8-abd8640b387f" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_9a2ec43c-4b04-4844-a2d8-abd8640b387f" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_9a2ec43c-4b04-4844-a2d8-abd8640b387f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> WebApi
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
    {
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> <span style="color: rgba(0, 0, 255, 1)">string</span> ConfigDir = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/root/k8s-config</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;

      ...
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>构造函数主要创建 cluster json 文件及目录,并把 json 内容反序列化成 cluster list。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_7737a7c4-f8a0-496f-a998-5576433077fa" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_7737a7c4-f8a0-496f-a998-5576433077fa" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_7737a7c4-f8a0-496f-a998-5576433077fa" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> <span style="color: rgba(0, 0, 255, 1)">string</span> _configName = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cluster.json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;Cluster&gt;<span style="color: rgba(0, 0, 0, 1)"> _clusters;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ClustersController()
{
    _clusters </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;Cluster&gt;<span style="color: rgba(0, 0, 0, 1)">()
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">Directory.Exists(Program.ConfigDir))
    {
      Directory.CreateDirectory(Program.ConfigDir);
   
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, _configName);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> json =<span style="color: rgba(0, 0, 0, 1)"> JsonConvert.SerializeObject(_clusters);
      System.IO.File.WriteAllText(configPath, json);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> json =<span style="color: rgba(0, 0, 0, 1)"> System.IO.File.ReadAllText(configPath);
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.IsNullOrWhiteSpace(json))
      {
            _clusters </span>= JsonConvert.DeserializeObject&lt;List&lt;Cluster&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">(json);
      }
    }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>获取集群列表,直接返回 cluster json list。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_849d8e77-65cd-4c17-aabb-e3c60c873cf6" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_849d8e77-65cd-4c17-aabb-e3c60c873cf6" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_849d8e77-65cd-4c17-aabb-e3c60c873cf6" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IActionResult List()
{
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(_clusters);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>获取指定集群的详情信息,并读取 kubernetes 证书信息进行展示。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_1ab3b0da-e102-4a80-9822-9edab8734e15" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_1ab3b0da-e102-4a80-9822-9edab8734e15" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_1ab3b0da-e102-4a80-9822-9edab8734e15" class="cnblogs_code_hide">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; Get(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> id)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> cluster = _clusters.SingleOrDefault(n =&gt; n.Id ==<span style="color: rgba(0, 0, 0, 1)"> id);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cluster == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> certificatePath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.Name);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> certificate = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.File.ReadAllTextAsync(certificatePath);
    cluster.Certificate </span>=<span style="color: rgba(0, 0, 0, 1)"> certificate;

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(cluster);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>获取指定集群的版本信息,主要使用 .net process + kubernetes 的证书执行&nbsp;kubectl version --short 指令获取版本信息。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_a63fe5af-39cc-4957-802c-cf540d2734c2" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_a63fe5af-39cc-4957-802c-cf540d2734c2" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_a63fe5af-39cc-4957-802c-cf540d2734c2" class="cnblogs_code_hide">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> IActionResult GetVersion(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> id)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> cluster = _clusters.SingleOrDefault(n =&gt; n.Id ==<span style="color: rgba(0, 0, 0, 1)"> id);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cluster == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.Name.ToLower());

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> command = $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubectl version --short --kubeconfig {configPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> (code, message) =<span style="color: rgba(0, 0, 0, 1)"> ExecuteCommand(command);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (code != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(StatusCodes.Status500InternalServerError, <span style="color: rgba(0, 0, 255, 1)">new</span> { Message =<span style="color: rgba(0, 0, 0, 1)"> message });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> lines =<span style="color: rgba(0, 0, 0, 1)"> message.Split(Environment.NewLine);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> version = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ClusterVersion
    {
      Client </span>= lines[<span style="color: rgba(128, 0, 128, 1)">0</span>].Replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Client Version:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty).Trim(),
      Server </span>= lines[<span style="color: rgba(128, 0, 128, 1)">1</span>].Replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Server Version:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty).Trim()
    };

    version.ClientNum </span>= <span style="color: rgba(0, 0, 255, 1)">double</span>.Parse(version.Client.Substring(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">));
    version.ServerNum </span>= <span style="color: rgba(0, 0, 255, 1)">double</span>.Parse(version.Server.Substring(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">));

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(version);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>创建集群,主要是上传集群证书,并把集群信息序列化成 json,并保存到文件。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_90af2dd3-32f8-4b95-be01-2c666da93c48" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_90af2dd3-32f8-4b95-be01-2c666da93c48" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_90af2dd3-32f8-4b95-be01-2c666da93c48" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt;<span style="color: rgba(0, 0, 0, 1)"> Create( Cluster cluster)
{
    cluster.Name </span>=<span style="color: rgba(0, 0, 0, 1)"> cluster.Name.ToLower();
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (_clusters.Any(n =&gt;<span style="color: rgba(0, 0, 0, 1)"> n.Name.Equals(cluster.Name)))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster name is existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> certificatePath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.Name);
    </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.File.WriteAllTextAsync(certificatePath, cluster.Certificate);

    cluster.Id </span>=<span style="color: rgba(0, 0, 0, 1)"> Guid.NewGuid().ToString();
    cluster.Certificate </span>= <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
    _clusters.Add(cluster);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> json =<span style="color: rgba(0, 0, 0, 1)"> JsonConvert.SerializeObject(_clusters);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, _configName);
    </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.File.WriteAllTextAsync(configPath, json);

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok();
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>更新集群,主要是更新集群证书及集群信息。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_8da25bba-f3da-4fd8-8bb6-3da294601f49" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_8da25bba-f3da-4fd8-8bb6-3da294601f49" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_8da25bba-f3da-4fd8-8bb6-3da294601f49" class="cnblogs_code_hide">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; Update(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> id, Cluster form)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> cluster = _clusters.SingleOrDefault(n =&gt; n.Id ==<span style="color: rgba(0, 0, 0, 1)"> id);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cluster == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> certificatePath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.Name);
    System.IO.File.Delete(certificatePath);

    cluster.Name </span>=<span style="color: rgba(0, 0, 0, 1)"> form.Name;
    certificatePath </span>=<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, form.Name);
    </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.File.WriteAllTextAsync(certificatePath, form.Certificate);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> json =<span style="color: rgba(0, 0, 0, 1)"> JsonConvert.SerializeObject(_clusters);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, _configName);
    </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.File.WriteAllTextAsync(configPath, json);

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok();
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>删除集群,主要是删除集群信息,并清理集群证书。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_57782998-72b9-42fd-bc4c-ef27061a3b9b" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_57782998-72b9-42fd-bc4c-ef27061a3b9b" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_57782998-72b9-42fd-bc4c-ef27061a3b9b" class="cnblogs_code_hide">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; Delete(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> id)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> cluster = _clusters.SingleOrDefault(n =&gt; n.Id ==<span style="color: rgba(0, 0, 0, 1)"> id);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cluster == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    _clusters.Remove(cluster);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> certificatePath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.Name);
    System.IO.File.Delete(certificatePath);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, _configName);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> json =<span style="color: rgba(0, 0, 0, 1)"> JsonConvert.SerializeObject(_clusters);
    </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.File.WriteAllTextAsync(configPath, json);

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> NoContent();
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>使用 .net process 执行 linux 指令的辅助函数。</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_207e0959-49a9-4456-8bc3-827aa5e94fe2" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_207e0959-49a9-4456-8bc3-827aa5e94fe2" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_207e0959-49a9-4456-8bc3-827aa5e94fe2" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> (<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">string</span>) ExecuteCommand(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> command)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> escapedArgs = command.Replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\"</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\\\"</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> process = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Process
    {
      StartInfo </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ProcessStartInfo
      {
            FileName </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
            Arguments </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-c \"{escapedArgs}\"</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
            RedirectStandardOutput </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
            RedirectStandardError </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
            UseShellExecute </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
            CreateNoWindow </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
      }
    };

    process.Start();
    process.WaitForExit();

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> message =<span style="color: rgba(0, 0, 0, 1)"> process.StandardOutput.ReadToEnd();
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (process.ExitCode != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      message </span>=<span style="color: rgba(0, 0, 0, 1)"> process.StandardError.ReadToEnd();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (process.ExitCode, message);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<h4><span class="pl-en">NamespacesController</span></h4>
<p><span class="pl-en">NamespacesController 比较简单,主要是使用 kubernetes 证书 + kubernetes api 获取集群的命名空间列表。</span></p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_a30c197e-7395-4156-8b2d-3a85a6d7245c" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_a30c197e-7395-4156-8b2d-3a85a6d7245c" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_a30c197e-7395-4156-8b2d-3a85a6d7245c" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span> IActionResult List( <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> cluster)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> config =<span style="color: rgba(0, 0, 0, 1)"> KubernetesClientConfiguration.BuildConfigFromConfigFile(configPath);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> k8s.Kubernetes(config);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> namespaces = client.ListNamespace().Items.Select(n =&gt;<span style="color: rgba(0, 0, 0, 1)"> n.Metadata.Name);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(namespaces);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<h4><span class="pl-en">PodsController</span></h4>
<p><span class="pl-en">PodsController 也比较简单,主要是获取指定命名空间下的 pod 列表,用于级联下拉菜单。</span></p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_41d46d07-7d2c-4df6-8330-213ed4ad7870" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_41d46d07-7d2c-4df6-8330-213ed4ad7870" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_41d46d07-7d2c-4df6-8330-213ed4ad7870" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span> IActionResult List( <span style="color: rgba(0, 0, 255, 1)">string</span> cluster, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> @namespace)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> config =<span style="color: rgba(0, 0, 0, 1)"> KubernetesClientConfiguration.BuildConfigFromConfigFile(configPath);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> k8s.Kubernetes(config);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> pods = client.ListNamespacedPod(@namespace).Items.Select(n =&gt;<span style="color: rgba(0, 0, 0, 1)"> n.Metadata.Name);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(pods);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<h4><span class="pl-en">ContainersController</span></h4>
<p><span class="pl-en">ContainersController 也比较简单,主要是获取指定 pod 里的容器列表,用于级联下拉菜单。</span></p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_aaea9ab6-741c-4203-9bf0-78277459eea2" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_aaea9ab6-741c-4203-9bf0-78277459eea2" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_aaea9ab6-741c-4203-9bf0-78277459eea2" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span> IActionResult List( <span style="color: rgba(0, 0, 255, 1)">string</span> cluster, <span style="color: rgba(0, 0, 255, 1)">string</span> @namespace, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> pod)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> config =<span style="color: rgba(0, 0, 0, 1)"> KubernetesClientConfiguration.BuildConfigFromConfigFile(configPath);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> k8s.Kubernetes(config);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> specificPod = client.ListNamespacedPod(@namespace).Items.Where(n =&gt; n.Metadata.Name ==<span style="color: rgba(0, 0, 0, 1)"> pod).First();
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> containers = specificPod.Spec.Containers.Select(n =&gt;<span style="color: rgba(0, 0, 0, 1)"> n.Name);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(containers);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<h4><span class="pl-en">FilesController</span></h4>
<p><span class="pl-en">FilesController 是最重要,同时也是稍微有点复杂的一个控制器。</span></p>
<p><span class="pl-en">获取容器指定路径的文件列表,主要是调用&nbsp;kubernetes api 的 exec 方法,执行指令 "ls -Alh --time-style long-iso {dir}" 获得文件内容信息。</span></p>
<p><span class="pl-en">由于 exec 是交互式的,所以方法使用的是 web socket:</span></p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_67ebe7a7-d6f9-47f3-9c2f-d1d11273afc0" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_67ebe7a7-d6f9-47f3-9c2f-d1d11273afc0" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_67ebe7a7-d6f9-47f3-9c2f-d1d11273afc0" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; List( <span style="color: rgba(0, 0, 255, 1)">string</span> cluster, <span style="color: rgba(0, 0, 255, 1)">string</span> @namespace, <span style="color: rgba(0, 0, 255, 1)">string</span> pod, <span style="color: rgba(0, 0, 255, 1)">string</span> container, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> dir)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> config =<span style="color: rgba(0, 0, 0, 1)"> KubernetesClientConfiguration.BuildConfigFromConfigFile(configPath);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> k8s.Kubernetes(config);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> webSocket = <span style="color: rgba(0, 0, 255, 1)">await</span> client.WebSocketNamespacedPodExecAsync(pod, @namespace, <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[] { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ls</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-Alh</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">--time-style</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">long-iso</span><span style="color: rgba(128, 0, 0, 1)">"</span>, dir }, container).ConfigureAwait(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> demux = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StreamDemuxer(webSocket);
    demux.Start();

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> buff = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">4096</span><span style="color: rgba(0, 0, 0, 1)">];
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> stream = demux.GetStream(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
    stream.Read(buff, </span><span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">4096</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> bytes =<span style="color: rgba(0, 0, 0, 1)"> TrimEnd(buff);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> text =<span style="color: rgba(0, 0, 0, 1)"> System.Text.Encoding.Default.GetString(bytes).Trim();

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> files =<span style="color: rgba(0, 0, 0, 1)"> ToFiles(text);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(files);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>再看一下指令 "ls -Alh --time-style long-iso {dir}" 的一个例子:</p>
<div class="cnblogs_code">
<pre>~ <span style="color: rgba(0, 0, 255, 1)">ls</span> -Alh --<span style="color: rgba(0, 0, 255, 1)">time</span>-style <span style="color: rgba(0, 0, 255, 1)">long</span>-iso /usr/<span style="color: rgba(0, 0, 0, 1)">bin
total 107M
lrwxrwxrwx.   </span><span style="color: rgba(128, 0, 128, 1)">1</span> root root      <span style="color: rgba(128, 0, 128, 1)">8</span>    <span style="color: rgba(128, 0, 128, 1)">2019</span>-<span style="color: rgba(128, 0, 128, 1)">02</span>-<span style="color: rgba(128, 0, 128, 1)">21</span> <span style="color: rgba(128, 0, 128, 1)">10</span>:<span style="color: rgba(128, 0, 128, 1)">47</span> ypdomainname -&gt; <span style="color: rgba(0, 0, 255, 1)">hostname</span>
-rwxr-xr-x.   <span style="color: rgba(128, 0, 128, 1)">1</span> root root      62K<span style="color: rgba(128, 0, 128, 1)">2018</span>-<span style="color: rgba(128, 0, 128, 1)">10</span>-<span style="color: rgba(128, 0, 128, 1)">30</span> <span style="color: rgba(128, 0, 128, 1)">17</span>:<span style="color: rgba(128, 0, 128, 1)">55</span> <span style="color: rgba(0, 0, 255, 1)">ar</span><span style="color: rgba(0, 0, 0, 1)">
drwxr</span>-xr-x      <span style="color: rgba(128, 0, 128, 1)">8</span> root root      <span style="color: rgba(128, 0, 128, 1)">4</span>.0K <span style="color: rgba(128, 0, 128, 1)">2022</span>-<span style="color: rgba(128, 0, 128, 1)">04</span>-<span style="color: rgba(128, 0, 128, 1)">01</span> <span style="color: rgba(128, 0, 128, 1)">09</span>:<span style="color: rgba(128, 0, 128, 1)">37</span> scripts</pre>
</div>
<p>第一行 total 开头的信息不太重要可以忽略,从第二行可以看出,每一行的格式是固定的。</p>
<p>第 0 列:权限,l 开头表示是链接,- 开头表示是文件,d 开头表示是文件夹</p>
<p>第 1 列:link 数量</p>
<p>第 2 列:用户</p>
<p>第 3 例:组</p>
<p>第 4 列:文件(夹)大小</p>
<p>第 5 列:日期</p>
<p>第 6 列:时间</p>
<p>第 7 列:文件(夹)名称</p>
<p>如果记录类型为 l,则文件名称为 7,8,9 列合成。</p>
<p>因此,解析代码如下:</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_4cc0a8db-6c3e-465b-ac53-9a02120f6b0f" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_4cc0a8db-6c3e-465b-ac53-9a02120f6b0f" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_4cc0a8db-6c3e-465b-ac53-9a02120f6b0f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> List&lt;FileItem&gt; ToFiles(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> text)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> files = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;FileItem&gt;<span style="color: rgba(0, 0, 0, 1)">();

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> lines =<span style="color: rgba(0, 0, 0, 1)"> text.Split(Environment.NewLine);

    </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> line <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> lines)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (line.StartsWith(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">total</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">))
      {
            </span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> trimLine =<span style="color: rgba(0, 0, 0, 1)"> line.Trim();
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> array = trimLine.Split(<span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(128, 0, 0, 1)">"</span>).ToList().Where(n =&gt; !<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.IsNullOrWhiteSpace(n)).ToList();

      </span><span style="color: rgba(0, 0, 255, 1)">var</span> file = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileItem
      {
            Permission </span>= array[<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">],
            Links </span>= array[<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">],
            Owner </span>= array[<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">],
            Group </span>= array[<span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">],
            Size </span>= array[<span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">],
            Date </span>= array[<span style="color: rgba(128, 0, 128, 1)">5</span><span style="color: rgba(0, 0, 0, 1)">],
            Time </span>= array[<span style="color: rgba(128, 0, 128, 1)">6</span><span style="color: rgba(0, 0, 0, 1)">],
            Name </span>= array[<span style="color: rgba(128, 0, 128, 1)">7</span><span style="color: rgba(0, 0, 0, 1)">]
      };

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (file.Permission.StartsWith(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">l</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">))
      {
            file.Name </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{array} {array} {array}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      files.Add(file);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> files;
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>上传文件主要是把文件上传到服务器,再使用 kubectl cp 指令把文件拷贝到指定容器中:</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_83518468-c499-478f-939e-ca856f28ba7f" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_83518468-c499-478f-939e-ca856f28ba7f" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_83518468-c499-478f-939e-ca856f28ba7f" class="cnblogs_code_hide">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; UploadFile( <span style="color: rgba(0, 0, 255, 1)">string</span> cluster, <span style="color: rgba(0, 0, 255, 1)">string</span> @namespace, <span style="color: rgba(0, 0, 255, 1)">string</span> pod, <span style="color: rgba(0, 0, 255, 1)">string</span> container, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> dir, IFormFile file)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> tmpPath = Path.Combine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/tmp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, System.Guid.NewGuid().ToString());
    </span><span style="color: rgba(0, 0, 255, 1)">await</span> <span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> stream =<span style="color: rgba(0, 0, 0, 1)"> System.IO.File.Create(tmpPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> file.CopyToAsync(stream);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> path =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(dir, file.FileName);
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> command = $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubectl cp {tmpPath} {pod}:{path} -c {container} -n {@namespace} --kubeconfig {configPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> (code, message) =<span style="color: rgba(0, 0, 0, 1)"> ExecuteCommand(command);

    System.IO.File.Delete(tmpPath);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (code == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(StatusCodes.Status500InternalServerError, <span style="color: rgba(0, 0, 255, 1)">new</span> { Message =<span style="color: rgba(0, 0, 0, 1)"> message });
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<p>下载文件主要是使用 kubectl cp 指令把文件从容器拷贝到服务器,再把文件读取下载:</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_a753337b-77ed-4404-a4b2-a8b1310352e2" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_a753337b-77ed-4404-a4b2-a8b1310352e2" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_a753337b-77ed-4404-a4b2-a8b1310352e2" class="cnblogs_code_hide">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; DownloadFile( <span style="color: rgba(0, 0, 255, 1)">string</span> cluster, <span style="color: rgba(0, 0, 255, 1)">string</span> @namespace, <span style="color: rgba(0, 0, 255, 1)">string</span> pod, <span style="color: rgba(0, 0, 255, 1)">string</span> container, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> path)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> tmpPath = Path.Combine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/tmp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, System.Guid.NewGuid().ToString());
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> command = $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubectl cp {pod}:{path} {tmpPath} -c {container} -n {@namespace} --kubeconfig {configPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> (code, message) =<span style="color: rgba(0, 0, 0, 1)"> ExecuteCommand(command);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (code != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(StatusCodes.Status500InternalServerError, <span style="color: rgba(0, 0, 255, 1)">new</span> { Message =<span style="color: rgba(0, 0, 0, 1)"> message });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> memory = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MemoryStream();
    </span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> stream = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileStream(tmpPath, FileMode.Open))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> stream.CopyToAsync(memory);
    }
    memory.Position </span>= <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> contentType =<span style="color: rgba(0, 0, 0, 1)"> GetContentType(tmpPath);

    System.IO.File.Delete(tmpPath);

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> File(memory, contentType);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>&nbsp;</p>
<h4>一个小插曲</h4>
<p>有个哥们提了一个 issue 提到 kubernetes 在 1.20 引入了一个新指令 "kubectl debug",目的是为了解决容器中未安装 bash 或者 sh 的问题。因此在新版本获取文件列表的方法中,我也实现了该指令:</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_88a599c4-be49-4528-8818-78a70b71a7a4" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_88a599c4-be49-4528-8818-78a70b71a7a4" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_88a599c4-be49-4528-8818-78a70b71a7a4" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;IActionResult&gt; List( <span style="color: rgba(0, 0, 255, 1)">string</span> cluster, <span style="color: rgba(0, 0, 255, 1)">string</span> @namespace, <span style="color: rgba(0, 0, 255, 1)">string</span> pod, <span style="color: rgba(0, 0, 255, 1)">string</span> container, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> dir)
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> configPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(Program.ConfigDir, cluster.ToLower());
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">System.IO.File.Exists(configPath))
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> BadRequest(<span style="color: rgba(0, 0, 255, 1)">new</span> { Message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cluster is not existed!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> command = $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubectl version --short --kubeconfig {configPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> (code, message) =<span style="color: rgba(0, 0, 0, 1)"> ExecuteCommand(command);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (code != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(StatusCodes.Status500InternalServerError, <span style="color: rgba(0, 0, 255, 1)">new</span> { Message =<span style="color: rgba(0, 0, 0, 1)"> message });
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> lines =<span style="color: rgba(0, 0, 0, 1)"> message.Split(Environment.NewLine);

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> version = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ClusterVersion
    {
      Client </span>= lines[<span style="color: rgba(128, 0, 128, 1)">0</span>].Replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Client Version:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty).Trim(),
      Server </span>= lines[<span style="color: rgba(128, 0, 128, 1)">1</span>].Replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Server Version:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty).Trim()
    };

    version.ClientNum </span>= <span style="color: rgba(0, 0, 255, 1)">double</span>.Parse(version.Client.Substring(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">));
    version.ServerNum </span>= <span style="color: rgba(0, 0, 255, 1)">double</span>.Parse(version.Server.Substring(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">));

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> text = <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (version.ClientNum &gt;= <span style="color: rgba(128, 0, 128, 1)">1.2</span> &amp;&amp; version.ServerNum &gt;= <span style="color: rgba(128, 0, 128, 1)">1.2</span><span style="color: rgba(0, 0, 0, 1)">)
    {
      command </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubectl debug -it {pod} -n {@namespace} --image=centos --target={container} --kubeconfig {configPath} -- sh -c 'ls -Alh --time-style long-iso {dir}'</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      (code, message) </span>=<span style="color: rgba(0, 0, 0, 1)"> ExecuteCommand(command);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (code != <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
      {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(StatusCodes.Status500InternalServerError, <span style="color: rgba(0, 0, 255, 1)">new</span> { Message =<span style="color: rgba(0, 0, 0, 1)"> message });
      }

      text </span>=<span style="color: rgba(0, 0, 0, 1)"> message;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> config =<span style="color: rgba(0, 0, 0, 1)"> KubernetesClientConfiguration.BuildConfigFromConfigFile(configPath);
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> k8s.Kubernetes(config);

      </span><span style="color: rgba(0, 0, 255, 1)">var</span> webSocket = <span style="color: rgba(0, 0, 255, 1)">await</span> client.WebSocketNamespacedPodExecAsync(pod, @namespace, <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[] { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ls</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-Alh</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">--time-style</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">long-iso</span><span style="color: rgba(128, 0, 0, 1)">"</span>, dir }, container).ConfigureAwait(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> demux = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StreamDemuxer(webSocket);
      demux.Start();

      </span><span style="color: rgba(0, 0, 255, 1)">var</span> buff = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">4096</span><span style="color: rgba(0, 0, 0, 1)">];
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> stream = demux.GetStream(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
      stream.Read(buff, </span><span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">4096</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> bytes =<span style="color: rgba(0, 0, 0, 1)"> TrimEnd(buff);
      text </span>=<span style="color: rgba(0, 0, 0, 1)"> System.Text.Encoding.Default.GetString(bytes).Trim();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">var</span> files =<span style="color: rgba(0, 0, 0, 1)"> ToFiles(text);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(files);
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>但意料之外的是,kubectl debug 里获取到的文件列表和容器的文件列表不一致,因此,如果想使用旧版本的方式,请使用&nbsp;d293e34&nbsp;版本的代码。</p>
<p>&nbsp;</p>
<h4>Cluster.razor 和 File.razor</h4>
<p>界面部分相对比较简单,基本就是 Bootstrap 和一些 http client 的调用,请大家自行去查阅即可,有问题可以留言讨论。</p>
<p>&nbsp;</p>
<h2>项目地址</h2>
<p>https://github.com/ErikXu/kubernetes-filesystem</p>
<p>欢迎大家 star,提 pr,提 issue,在文章留言交流,或者在公众号 - 跬步之巅留言交流。</p><br><br>
来源:https://www.cnblogs.com/Erik_Xu/p/16183765.html
頁: [1]
查看完整版本: 使用 .net + blazor 做一个 kubernetes 开源文件系统