Container in Windows

2022-09-15 10:47:47 浏览数 (1)

Mechanism

Linux容器是指一个或者一组从系统中隔离出来的进程,运行此进程的所有文件均由一个image提供。由此定义我们知道容器包含两个点一是隔离,二是运行文件的封装。其中隔离包含cpu,memory隔离,网络隔离,文件系统隔离,设备可见性隔离等。下面来分段介绍windows平台如何实现以上几种隔离。文件封装沿用docker image本文不再详细阐述。内容较多使用Windows容器运行UE4渲染任务会单独拆分一个文章介绍。

PS. 由于Windows 容器特性以及k8s对其的支持都处于快速迭代的状态本文内容主要基于 k8s v1.20.x和windows server lts 2019.

Isolation

windows主要支持两种隔离方式:一种是模拟Linux cgroup的原理实现了一套namespace机制以实现进程级别的隔离共享windows内核。一种是Hyper-V隔离,通过Hyper-V启动优化过的轻量化虚拟机来运行每个容器类似与kata runtime的思路,每个虚拟机有自己独立的内核,可以运行与host不同版本的windows虚拟机以及运行Linux容器(除了Azure应该没人这么干,k8s也不支持hyper-v隔离)以下是两种隔离模式的架构。

由于windows闭源的特性很多信息都是隐藏,以下部分主要基于以下几篇文章1,2,3,4,5。微软在dockecon 16上有对windwos容器原理的介绍,感兴趣的可以去Google一下视频 背景知识 windows NT kernle中的processes management JobObjects, which is a kernel object that allows you to manage a group of processes as a single unit. 对job objects的修改会对job内的所有进程生效。 以下几种limits sets: UI相关限制比如,读写clipboard等 CPU使用频率,设置允许使用的最大CPU time 进程优先级,jobs内的所有进程处于一个相同的优先级 完整的API可以参考win32文档

  • namespace 在windows os中并不叫做namespace,而是叫做silo, 是一种对windows job object的扩展,是对一组进程的限制,支持 注册表,进程ID,文件系统,网络, object namespace上的隔离
  • object namespace 是windows中用来保护object的非授权访问机制在windows nt内核中 c:windows 其实是 DosDevicesC:windows,这里包含了windows所有对象的访问endpoint(系统级object namespace对用户隐藏)。silo提供了一种类似与chroot的隔离方案,会将容器的DosDevicesC: 映射到SilosfooDosDevicesC:

windows引入了 host compute service(HCS)来管理容器,并且提供了golang SDK

Network

Windows 容器网络方案使用来自于Hyper-V的虚拟网络技术。但是不像Hyper-V虚拟机可以让用户自己管理虚拟机网卡,虚拟交换机设定各种参数。容器环境下使用Host Compute Network(HCN)一套接口来管理容器网络。这里借用一下官网的汇总架构图

HCN会为每个network创建一个专用的hyper-v virtual switch,并且管理NAT策略以及IP池。启动容器时HCN会在每个network namespace 下面创建一个container endpoint(除NAT模式下每个endpoint对应一个vNIC),并且下发IP,DNS,routes规则。

容器运行时使用HCN初始化好的namespace来调用HCS创建容器。

HCN支持如下的网络模式

Network Type

Container to Container(same node)

Container to external

Contaienr to Container(cross node)

IPAM

NAT

每个容器一个VNIC挂到Vswitch上不可与host network冲突 同Vswitch的容器可以通信

全部流量走host nic NAT 使用winNAT模块

不支持

HCN管理IP

Transparent

每个容器一个VNIC与host netnetwork在同一个三层网络中

容器内流量直接在物理网络上路由

直接通过物理网络通信

静态IP或物理网络提供DHCP

Overlay

同子网内通过vswitch直接交换 跨子网通过本机NIC路由

不直接支持,需要一个虚拟endpoint提供NAT策略

VXLAN封装之后通过NIC路由

由具体的CNI实现IP管理

L2Bridge

同子网通过hyper-v switch通信 跨子网改写mac后由host路由

出入流量在host nic上做二层mac重写

重写mac之后由host nic进行路由

HCN管理IP

L2Tunnel(Azure only)

azure直接下发hyper-v switch策略

通过azure virtual network gateway处理

同单一node内通信类似

azure cni

自己创建network的例子:

代码语言:text复制
$network = New-HNSNetwork -Type $Global:NetworkModel -DNSServer "183.69.83.19,183.60.82.98" -AddressPrefix "169.254.255.248/29" -Gateway "169.254.255.249" -Name $NetworkName -Verbose

# if use l2bridge, create a quasi gateway for the containers to use
if ($Global:NetworkModel -eq "L2bridge") {
    # Enable forwarding
    netsh int ipv4 set int "vEthernet (Ethernet)" for=en
    $hnsEndpoint = New-HnsEndpoint -NetworkId $network.ID -Name $endpointName -IPAddress 169.254.255.250 -Verbose 
    Attach-HnsHostEndpoint -EndpointID $hnsEndpoint.Id -CompartmentID 1 
    netsh int ipv4 set int $vnicName for=en
    # add a static ARP entry with a dummy MAC so that traffic is able to leave our host without being stuck waiting for an ARP probe to resolve this gateway IP
    netsh int ipv4 add neighbors $vnicName "169.254.255.249" "00-01-e8-8b-2e-4b"
}

route delete 169.254.0.0
# after create hns network, a route of 169.254.0.0/17 must be added
route add 169.254.0.0 mask 255.255.0.0 $podGW metric 10
route -p add 169.254.0.0 mask 255.255.0.0 169.254.255.249

Storage

Windows文件系统也非常复杂。随着不断的添加新的特性进去,Transactions, File IDs, USN journal 等等,而Windows程序很多依赖于这些特性。

因此如果要基于此去做一个Linux世界中的AUFS,或者OverlayFS,但是支持上面这些NTFS特性的,极为困难。于是在 Windows Container v1 中,采用了类似于Device Mapper的办法 ,所不同的是,块设备是虚的,所以是 虚拟块设备 每个容器的NTFS 分区 的方式。使用 symlink 到宿主文件系统各层,这样让块设备不会过于臃肿。

K8S中存储方案主要转向CSI。CSI设计中每个node需要一个daemonset pod或者sidecar 容器来完成volume 格式化,挂载等操作,pod需要具备privileged或者file system admin权限。而Windows中并不支持对一个容器进行提权操作,所以在Windows 平台上产生了csi-proxy 方案

入上图所示简单来说这个方案是在容器所在的windows node上单独运行一个cis-proxy的进程来完成对disk,volume的一系列操作,规避掉容器内权限不足的问题。

csi-proxy方案只是一个无奈的折衷。从kubelet到最终执行操作的csi-proxy.exe走了四层的grpc io,链路冗长,而且csi-proxy的实现还是golang cmd.exec powershell command实现,性能不高,错误处理依赖文本处理。并且添加一个特性需要修改三个项目,维护成本极高。

在k8s 支持windows host container

PS. kubernetes 1.23 中windows host process container(windows server lts 2022)特性进入beta,csi-proxy变得比较鸡肋。个人感觉社区未来会放弃这个方案 PPS. windows host process container允许容器进行mount操作。同时支持host network、runasuser、runasnonroot

Devices

在Linux平台devices只需要将/dev/下的设备描述符文件mount进入容器便可以实现仅某个容器的设备可见。在windows 平台则要分情况来看

对于Hyper-v隔离的容器,无论容器是Linux还是windows都无法支持任何硬件设备挂入容器

对于process-isolated容器 则有条件支持硬件设备挂入

1.版本要求:

windows host os 2019 or windows 10, version 1809 or newer container base image must be 1809 or later docker enginer 19.03 or newer, containerd must include this PR

2.使用设备的 device interface class GUID挂载设备

简单来说device interface class guid 是用来标记某一类别硬件驱动的唯一id,需要注意的是这里标记的是驱动的类别而不是某个硬件设备。所以把一个驱动的class guid mount进入容器之后这类驱动之下的所有设备都会对一个容器可见。无法像Linux一样实现单一硬件设备的可见性隔离。

支持的设备类型和GUID如下:

Device Type

Interface Class GUID

GPIO

916EF1CB-8426-468D-A6F7-9AE8076881B3

I2C Bus

A11EE3C6-8421-4202-A3E7-B91FF90188E4

COM Port

86E0D1E0-8089-11D0-9CE4-08003E301F73

SPI Bus

DCDE6AF9-6610-4285-828F-CAAF78C424CC

DirectX GPU Acceleration

5B45201D-F2F2-4F3B-85BB-30FF1F953599

全部的class guid可以查阅MicrosoftDocs/windows-driver-docs

对于GPU还有特殊要求,上表中GPU一行设备类型是 DirectX GPU也就是windows容器必须使用支持DirectX的GPU驱动才可以,并且用户程序也必须是使用DirectX框架访问GPU,其它类型一律不支持。

PS. NVIDIA Windows Data center driver 并不支持DX,这里必须使用GRID driver,GRID driver需要另外收取授权费用。

serverless tke(eks)上使用的windows os已经部署了device-plugin来支持nvidia gpu的使用

Practice

Use Windows container on serverless tke cluster

windows 容器正式上线之后可以在页面操作添加windows节点 仅 1.20 集群支持Windows容器,更高版本正在接入中

  1. TKE集群添加超级节点池并添加一个按量超级节点(serverless tke 集群忽略)
  2. 使用以下yaml模板,创建windows node。对应值可以从1中添加的超级节点或者serverless集群中其它节点获取
代码语言:yaml复制
apiVersion: v1
kind: Node
metadata:
  annotations:
    eks.tke.cloud.tencent.com/subnet-id: $SUBENET # subnet-xxxxxxx
    eks.tke.cloud.tencent.com/zone-id: "$ZONEID" # 数字化的zone id
  labels:
    beta.kubernetes.io/arch: amd64
    beta.kubernetes.io/os: windows
    eks.tke.cloud.tencent.com/subnet-id: $SUBENET # subnet-xxxxxxx
    eks.tke.cloud.tencent.com/version: v2
    eks.tke.cloud.tencent.com/zone-name: ap-$REGION-$ZNOENUMBER # ap-guangzhou-7
    failure-domain.beta.kubernetes.io/region: $REGIONNAME # gz
    failure-domain.beta.kubernetes.io/zone: "$ZONEID" # 数字化的zone id
    kubernetes.io/arch: amd64
    kubernetes.io/hostname: eklet-$SUBENET # subnet-xxxxxxx
    kubernetes.io/os: windows
    node.kubernetes.io/windows-build: '10.0.17763'
    node.kubernetes.io/instance-type: eklet
    topology.com.tencent.cloud.csi.cbs/zone: ap-$REGION-$ZNOENUMBER # ap-guangzhou-7
    topology.kubernetes.io/region: $REGIONNAME # gz
    topology.kubernetes.io/zone: "$ZONEID" # 数字化的zone id
  name: eklet-$SUBENET-win # subnet-xxxxxxx
spec:
  taints:
  - effect: NoSchedule
    key: os
    value: windows
  1. 创建runtime class
代码语言:yaml复制
apiVersion: node.k8s.io/v1
handler: runhcs-wcow-process
kind: RuntimeClass
metadata:
  name: windows
scheduling:
  nodeSelector:
    kubernetes.io/arch: amd64
    kubernetes.io/os: windows
  tolerations:
  - effect: NoSchedule
    key: os
    operator: Equal
    value: windows

以上就可以在此集群中创建windows 容器了:

代码语言:yaml复制
kind: Pod
apiVersion: v1
metadata:
  name: test-pod-1
spec:
  runtimeClassName: windows
  containers:
    - name: test-container-1
      env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
      image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
      ports:  # 为了告知windows cni创建 port-mapping规则,需要显示的写上希望暴露到pod ip上的端口
      - containerPort: 80  
      - containerPort: 443
  restartPolicy: Always

Image cache

windows容器正式上线之后可以通过前端管理镜像缓存,目前需要通过CLI来创建,其它操作可以从web端处理。

Windows base image 往往都在7G以上,而容器中的可用空间只有 19.9G,算上解压时需要的临时空间,所以tke上windows仅支持最大8G的镜像。即使8G以内镜像启动之后随着运行也会导致C盘爆满,os无法正常运行。

为了规避这个问题推荐windows容器都启用镜像缓存功能,详细描述参考。

创建例子如下,二进制在本文附件中

代码语言:shell复制
./tkeclient image-cache create --region ap-xxxxxx --secretID XXXXXXX --secretKey XXXXXXX --images mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019  --name windows-images-cache-01  --os windows --size 100 --subnetID subnet-xxxx --vpcID vpc-xxxx --imageUser xxxxxxxx --imagePasswd xxxxx

--size 的单位是G 最好设置比镜像大一倍,至少是image size 最大的5个层的size,否则可能遇到解压镜像层空间不足

--images 指定需要缓存的image,image base windows image必须是 1809或者server 2019

Pod with CFS

0.创建CFS,创建时选择SMB协议,不要使用NFS。

  1. 创建csidriver
代码语言:yaml复制
apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
  name: smb.csi.k8s.io
spec:
  attachRequired: false
  podInfoOnMount: true
  1. 创建pv
代码语言:yaml复制
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-smb-guest
spec:
  capacity:
    storage: 80Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
    - dir_mode=0777
    - file_mode=0777
    - vers=3.0
    - guest
  csi:
    driver: smb.csi.k8s.io
    readOnly: false
    volumeHandle: public-108  # make sure it's a unique id in the cluster
    volumeAttributes:
      source: "//192.168.0.108/public"
  1. 创建pvc
代码语言:yaml复制
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-smb-guest
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 80Gi
  volumeName: pv-smb-guest
  storageClassName: "" # 避免匹配到默认stroageClass
  1. 创建pod mount pvc
代码语言:yaml复制
kind: Pod
apiVersion: v1
metadata:
  name: test-pod-2
  annotations:
spec:
  runtimeClassName: windows
  containers:
    - name: test-container-1
      env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
      image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
      ports:
      - containerPort: 80
      - containerPort: 443
      volumeMounts:
      - mountPath: /data
        name: smb
  restartPolicy: Always
  volumes:
  - name: smb
    persistentVolumeClaim:
      claimName: pvc-smb-guest

更多参数参考csi-driver-smb

tkeclient-darwin.zip
tkeclient-linux.zip
tkeclient-win.zip

0 人点赞