几年前,我们发表了一篇关于使用Docker和Kubernetes部署一个Rust应用程序的博文。那个应用是一个Telegram机器人。我们今天要做类似的事情,但有一些有意义的区别:
- 我们要部署的是一个网络应用。不要太兴奋:这将是一段非常简单的代码,基本上是从actix-web文档中复制的。
- 我们将在Github行动上建立部署镜像
- 而且我们将使用Windows容器而不是Linux来构建。(抱歉埋下了伏笔。)
当我们在FP Complete的Kubernetes管理产品Kube360®中推出Windows支持时,我们把这个放在一起进行测试。我想把这个帖子放在一起,以证明几件事:
- 与更熟悉的Linux方法相比,Windows容器的工作流程是多么令人愉快和熟悉。
- Github Actions在构建Windows容器方面的无缝工作
- 通过正确的配置,Kubernetes是一个部署Windows容器的伟大平台。
- 当然,Rust工具链在Windows上也是非常棒的。
好了,让我们开始吧
前提条件
在我们深入讨论之前,先说一下。Windows容器只能在Windows机器上运行。甚至不是所有的Windows机器都支持Windows Containers。你需要Windows 10 Pro或类似的许可证,并在该机器上安装Docker。你还需要确保Docker被设置为使用Windows而不是Linux容器。
如果你已经设置好了这些,你就可以按照下面的大部分步骤进行操作。如果没有,你将无法在你的本地机器上构建或运行Docker镜像。
另外,为了在Kubernetes上运行该应用程序,你需要一个带有Windows节点的Kubernetes集群。在这篇博文中,我将在Azure上使用FP Complete Kube360测试集群,尽管我们之前也在AWS和内部集群上进行过测试。
Rust应用程序
到目前为止,这个应用程序的源代码将是这篇文章中最无趣的部分。如前所述,它基本上是一个直接从actix-web文档中复制粘贴的例子,具有可变的状态。事实证明,这是一个测试Kubernetes基本功能的好方法,如健康检查、复制和自动修复。
我们将使用写这篇文章时最新的稳定的Rust版本来构建这个文件,所以要创建一个带有内容的rust-toolchain 文件。
1.47.0
我们的Cargo.toml 文件将是非常普通的,只是加入了对actix-web 的依赖。
[package]
name = "windows-docker-web"
version = "0.1.0"
authors = ["Michael Snoyman <msnoyman@fpcomplete.com>"]
edition = "2018"
[dependencies]
actix-web = "3.1"
如果你想看看我编译的Cargo.lock 文件,它可以在源码库中找到。
最后是src/main.rs 中的实际代码。
use actix_web::{get, web, App, HttpServer};
use std::sync::Mutex;
struct AppState {
counter: Mutex<i32>,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> String {
let mut counter = data.counter.lock().unwrap();
*counter += 1;
format!("Counter is at {}", counter)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let host = "0.0.0.0:8080";
println!("Trying to listen on {}", host);
let app_state = web::Data::new(AppState {
counter: Mutex::new(0),
});
HttpServer::new(move || App::new().app_data(app_state.clone()).service(index))
.bind(host)?
.run()
.await
}
这段代码创建了一个应用状态(一个i32 的突变),定义了一个单一的GET 处理程序来增加该变量并打印出当前值,然后将其托管在0.0.0.0:8080 上。不算太寒酸。
如果你跟着代码走,现在是一个好时机,cargo run ,并确保你能够在你的localhost:8080 上加载该网站。
Docker文件
如果这是你第一次接触Windows容器,你可能会惊讶地听到我说 "Dockerfile"。 Windows容器镜像可以用你在Linux世界中习惯的那种Dockerfiles来构建。这甚至支持更高级的功能,比如多阶段Dockerfile,我们在这里要利用它。
微软为Windows容器提供了许多不同的基础镜像。我们将使用Windows Server Core。它为安装Rust依赖项提供了足够的能力(我们很快就会看到),而没有包括太多不需要的额外内容。Nanoserver是一个更轻量级的镜像,但它与我们用于-msvc Rust目标的Microsoft Visual C++运行时不兼容。
注意我选择在这里使用-msvc ,而不是-gnu ,有两个原因。首先,它更接近我们在Kube360中需要支持的实际用例,因此成为一个更好的测试案例。此外,作为Windows上Rust的默认目标,它似乎是合适的。如果有人对一个 "有趣 "的副业感兴趣,应该可以在-gnu 目标的基础上建立一个更简约的基于nanoserver的镜像。
完整的Docker文件可以在Github上找到,但让我们更仔细地看一下。如前所述,我们将执行一个多阶段的构建。我们将从构建镜像开始,它将安装Rust构建工具链并编译我们的应用程序。我们首先使用Windows Server Core基础镜像,并将外壳切换回标准的cmd.exe 。
FROM mcr.microsoft.com/windows/servercore:1809 as build
# Restore the default Windows shell for correct batch processing.
SHELL ["cmd", "/S", "/C"]
接下来我们要安装构建Rust代码所需的Visual Studio buildtools。
# Download the Build Tools bootstrapper.
ADD https://aka.ms/vs/16/release/vs_buildtools.exe /vs_buildtools.exe
# Install Build Tools with the Microsoft.VisualStudio.Workload.AzureBuildTools workload,
# excluding workloads and components with known issues.
RUN vs_buildtools.exe --quiet --wait --norestart --nocache \
--installPath C:\BuildTools \
--add Microsoft.Component.MSBuild \
--add Microsoft.VisualStudio.Component.Windows10SDK.18362 \
--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 \
|| IF "%ERRORLEVEL%"=="3010" EXIT 0
然后我们将修改入口,包括使用这些构建工具所需的环境修改。
# Define the entry point for the docker container.
# This entry point starts the developer command prompt and launches the PowerShell shell.
ENTRYPOINT ["C:\\BuildTools\\Common7\\Tools\\VsDevCmd.bat", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"]
接下来是安装rustup ,幸运的是这很容易。
RUN curl -fSLo rustup-init.exe https://win.rustup.rs/x86_64
RUN start /w rustup-init.exe -y -v && echo "Error level is %ERRORLEVEL%"
RUN del rustup-init.exe
RUN setx /M PATH "C:\Users\ContainerAdministrator\.cargo\bin;%PATH%"
然后我们复制相关的源文件并启动构建,将生成的可执行文件存储在c:\output 。
COPY Cargo.toml /project/Cargo.toml
COPY Cargo.lock /project/Cargo.lock
COPY rust-toolchain /project/rust-toolchain
COPY src/ /project/src
RUN cargo install --path /project --root /output
这样,我们就完成了我们的构建!是时候跳转到我们的运行时图像了。在这个镜像中我们不需要Visual Studio buildtools,但我们需要Visual C++运行时。
FROM mcr.microsoft.com/windows/servercore:1809
ADD https://download.microsoft.com/download/6/A/A/6AA4EDFF-645B-48C5-81CC-ED5963AEAD48/vc_redist.x64.exe /vc_redist.x64.exe
RUN c:\vc_redist.x64.exe /install /quiet /norestart
有了这个,我们就可以从构建镜像中复制我们的可执行文件,并将其设置为镜像中的默认CMD 。
COPY --from=build c:/output/bin/windows-docker-web.exe /
CMD ["/windows-docker-web.exe"]
就这样,我们已经有了一个真实的Windows容器。如果你愿意,你可以通过运行来测试它。
> docker run --rm -p 8080:8080 fpco/windows-docker-web:f8a3192e63f2e699cc67716488a633f5e0893446
如果你连接到8080端口,你应该会看到我们这个简单得令人痛苦的应用程序。万岁!
使用Github动作进行构建
使用多阶段Docker文件进行构建的一个好处是,我们的CI脚本变得非常简单。我们的脚本不需要用正确的构建工具或任何其他配置来设置环境,而是:
- 登录到Docker Hub注册处
- 执行一个
docker build - 推送到Docker Hub注册中心
缺点是,这种设置没有构建缓存的作用。有多种方法可以缓解这个问题,比如创建帮助性的构建镜像,预先烘烤依赖项。或者你可以在CI上的主机上执行构建,只使用Docker文件来生成运行时镜像。这些都是有趣的调整,下次再试。
虽然采取了简单的多阶段方法,但我们的.github/workflows/container.yml 文件中有以下内容。
name: Build a Windows container
on:
push:
branches: [master]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Build and push
shell: bash
run: |
echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login --username fpcojenkins --password-stdin
IMAGE_ID=fpco/windows-docker-web:$GITHUB_SHA
docker build -t $IMAGE_ID .
docker push $IMAGE_ID
我喜欢遵循惯例,用提交的Git SHA来标记我的图像。其他人喜欢不同的标签方案,这都是由你决定的。
宣言文件
现在我们有了一个工作的Windows容器镜像,下一步是将其部署到我们的Kube360集群。一般来说,我们使用ArgoCD和Kustomize来管理Kube360内的应用部署,这让我们保持一个非常好的Gitops工作流程。相反,在这篇博文中,我将向你展示原始清单文件。这也会让我们玩玩k3 命令行工具,它也恰好是用Rust写的。
首先,我们将有一个部署清单来管理运行应用程序本身的pod。由于这是一个简单的Rust应用程序,我们可以对其进行非常低的资源限制。我们将禁用Istio侧边栏,因为它与Windows不兼容。我们将要求Kubernetes使用Windows机器来托管这些pod。我们还将设置一些基本的健康检查。综上所述,这就是我们的清单文件的样子。
apiVersion: apps/v1
kind: Deployment
metadata:
name: windows-docker-web
labels:
app.kubernetes.io/component: webserver
spec:
replicas: 1
minReadySeconds: 5
selector:
matchLabels:
app.kubernetes.io/component: webserver
template:
metadata:
labels:
app.kubernetes.io/component: webserver
annotations:
sidecar.istio.io/inject: "false"
spec:
runtimeClassName: windows-2019
containers:
- name: windows-docker-web
image: fpco/windows-docker-web:f8a3192e63f2e699cc67716488a633f5e0893446
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 128Mi
cpu: 100m
太棒了,这是迄今为止三个清单中最复杂的一个。接下来,我们将在该部署前放置一个相当标准的服务。
apiVersion: v1
kind: Service
metadata:
name: windows-docker-web
labels:
app.kubernetes.io/component: webserver
spec:
ports:
- name: http
port: 80
targetPort: http
type: ClusterIP
selector:
app.kubernetes.io/component: webserver
这在80端口暴露了一个服务,并针对部署中的http 端口(8080端口)。最后,我们有我们的Ingress。Kube360使用外部DNS来自动设置DNS记录,并使用cert-manager来自动获取TLS证书。我们的清单看起来像这样。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-ingress-prod
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
name: windows-docker-web
spec:
rules:
- host: windows-docker-web.az.fpcomplete.com
http:
paths:
- backend:
serviceName: windows-docker-web
servicePort: 80
tls:
- hosts:
- windows-docker-web.az.fpcomplete.com
secretName: windows-docker-web-tls
现在,我们在Docker镜像里有我们的应用程序,我们有清单文件来指导Kubernetes如何运行它,我们只需要部署这些清单就可以了。
启动
有了我们的清单,我们终于可以部署它们了。你可以直接使用kubectl 来完成这项工作。由于我正在部署到Kube360,我将使用k3 命令行工具,它可以自动登录,获得临时的Kubernetes证书,并通过环境变量向kubectl 命令提供这些证书。这些步骤可以在Windows、Mac或Linux上运行。但由于我们已经在Windows上完成了这篇文章的其余部分,所以我也将使用我的Windows机器来做这个。
> k3 init test.az.fpcomplete.com
> k3 kubectl apply -f deployment.yaml
Web browser opened to https://test.az.fpcomplete.com/k3-confirm?nonce=c1f764d8852f4ff2a2738fb0a2078e68
Please follow the login steps there (if needed).
Then return to this terminal.
Polling the server. Please standby.
Checking ...
Thanks, got the token response. Verifying token is valid
Retrieving a kubeconfig for use with k3 kubectl
Kubeconfig retrieved. You are now ready to run kubectl commands with `k3 kubectl ...`
deployment.apps/windows-docker-web created
> k3 kubectl apply -f ingress.yaml
ingress.networking.k8s.io/windows-docker-web created
> k3 kubectl apply -f service.yaml
service/windows-docker-web created
我告诉k3 ,使用test.az.fpcomplete.com 集群。在第一次调用k3 kubectl ,它检测到我没有集群的有效凭证,并打开我的浏览器,让我登录到一个页面。Kube360的设计目标之一是大力利用现有的身份提供者,如Azure AD、Google Directory、Okta、Microsoft 365和其他。这不仅比复制粘贴kubeconfig 文件的永久凭证更安全,而且更方便用户使用。正如你所看到的,上面的过程是相当自动化的。
检查豆荚是否真正运行和健康是很容易的
> k3 kubectl get pods
NAME READY STATUS RESTARTS AGE
windows-docker-web-5687668cdf-8tmn2 1/1 Running 0 3m2s
最初,在获取TLS证书时,入口控制器看起来像这样
> k3 kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-zlq6j <none> windows-docker-web.az.fpcomplete.com 80 0s
windows-docker-web <none> windows-docker-web.az.fpcomplete.com 80, 443 3s
而在cert-manager得到TLS证书后,它将切换到
> k3 kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
windows-docker-web <none> windows-docker-web.az.fpcomplete.com 52.151.225.139 80, 443 90s
最后,我们的网站上线了!万岁,一个为Windows编译的Rust网络应用,在Azure内部的Kubernetes上运行。
注意根据你阅读这篇文章的时间,网络应用可能仍在运行,也可能不在运行,所以如果你试图连接到该主机时没有得到回应,不要感到惊讶。
总结
这篇文章中实际的Rust代码有点少,但大量的Windows脚本。我想很多Rustaceans已经知道,Rust在Windows上的开发体验是一流的。可能不明显的是,Docker在Windows上的体验是多么令人愉快。肯定有一些痛点,比如涉及到的大型图像和需要安装VC运行时。但总的来说,只要有一点货物培养,就不会太糟糕。最后,通过Kube360准备了一个支持Windows的集群,使部署变得轻而易举。