使用 .net + blazor 做一个 kubernetes 开源文件系统
<h2>背景</h2><p>据我所知,目前 kubernetes 本身或者其它第三方社区都没提供 kubernetes 的文件系统。也就是说要从 kubernetes 的容器中下载或上传文件,需要先进入容器查看目录结构,然后再通过 kubectl cp 指令把文件拷贝进或出容器。虽然说不太麻烦,但也不太方便。当时正好推出 .net 5 + blazor,就趁着这个机会使用 .net 5 + blazor 做一个 kubernetes 的开源文件系统。</p>
<p> </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> </p>
<h4>浏览、上传、下载文件</h4>
<p>创建完集群后,就可以方便地选择集群 -> 命名空间 -> Pod -> 容器,然后浏览容器目录,上传文件到容器,或者下载文件到本地:</p>
<p><img src="https://img2022.cnblogs.com/blog/182190/202204/182190-20220424233126656-103899502.png" alt="" loading="lazy"></p>
<h2> </h2>
<h2>使用方法</h2>
<ol>
<li>克隆代码,https://github.com/ErikXu/kubernetes-filesystem</li>
<li>安装 docker</li>
<li>执行 bash build.sh 指令</li>
<li>执行 bash pack.sh 指令</li>
<li>下载 kubectl 并保存到 /usr/local/bin/kubectl</li>
<li>执行 bash run.sh 指令</li>
</ol>
<p> </p>
<h2>代码目录</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:false;">├── build.sh # 构建脚本
├── docker # docker 目录
│ └── Dockerfile # Dockerfile
├── pack.sh # 打包脚本
├── publish.sh # 发布脚本
├── README_CN.md # 项目说明(中文)
├── README.md # 项目说明
├── run.sh # 运行脚本
└── src # 源码目录
├── Kubernetes.Filesystem.sln # 解决方案
├── Web # Web 项目
│ ├── App.razor # 入口 APP
│ ├── _Imports.razor # 引用文件
│ ├── Pages
│ │ ├── Cluster.razor # 集群管理页面
│ │ └── File.razor # 文件管理页面
│ ├── Shared
│ │ ├── MainLayout.razor # 布局文件
│ │ ├── MainLayout.razor.css # 布局样式文件
│ │ ├── NavMenu.razor # 导航文件
│ │ ├── NavMenu.razor.css # 导航样式文件
│ │ └── SurveyPrompt.razor # 调查弹出框
│ ├── Web.csproj # Web 项目文件
│ └── wwwroot
│ ├── css # 样式文件夹
│ ├── favicon.ico # icon 文件
│ └── index.html # html 入口页
└── WebApi # WebApi 项目
├── appsettings.Development.json # 开发环境配置文件
├── appsettings.json # 配置文件
├── Controllers # 控制器目录
│ ├── ClustersController.cs # 集群控制器
│ ├── ContainersController.cs # 容器控制器
│ ├── FilesController.cs # 文件控制器
│ ├── NamespacesController.cs # 命名空间控制器
│ └── PodsController.cs # Pod 控制器
├── Startup.cs # Startup 文件
└── WebApi.csproj # WebApi 项目文件</pre>
</div>
<h2> </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> </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<Cluster><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<Cluster><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<List<Cluster>><span style="color: rgba(0, 0, 0, 1)">(json);
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> </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> </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<IActionResult> 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 => 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> </p>
<p>获取指定集群的版本信息,主要使用 .net process + kubernetes 的证书执行 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 => 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> </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<IActionResult><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 =><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> </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<IActionResult> 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 => 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> </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<IActionResult> 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 => 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> </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> </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 =><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> </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 =><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> </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 => 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 =><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> </p>
<h4><span class="pl-en">FilesController</span></h4>
<p><span class="pl-en">FilesController 是最重要,同时也是稍微有点复杂的一个控制器。</span></p>
<p><span class="pl-en">获取容器指定路径的文件列表,主要是调用 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<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> 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> </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 -> <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<FileItem> 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<FileItem><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 => !<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> </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<IActionResult> 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> </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<IActionResult> 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> </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<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> 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 >= <span style="color: rgba(128, 0, 128, 1)">1.2</span> && version.ServerNum >= <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 里获取到的文件列表和容器的文件列表不一致,因此,如果想使用旧版本的方式,请使用 d293e34 版本的代码。</p>
<p> </p>
<h4>Cluster.razor 和 File.razor</h4>
<p>界面部分相对比较简单,基本就是 Bootstrap 和一些 http client 的调用,请大家自行去查阅即可,有问题可以留言讨论。</p>
<p> </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]