1. 程式人生 > 實用技巧 >一種用buildkit打造免registry的local cd/ci工具,打通vscodeonline與openfaas模擬cloudbase打造碎片化程式設計開發部署環境的設想

一種用buildkit打造免registry的local cd/ci工具,打通vscodeonline與openfaas模擬cloudbase打造碎片化程式設計開發部署環境的設想

本文關鍵字:如何直接修改docker中的檔案,從外部編輯dockernamespace內檔案,share data between host and container?,定製映象和容器,不經過任何registry重建/修改/commit docker映象,Creating an image from a commited snapshot,把openfaas還原為非docker結構,可以直接在docker內編輯整合,overlay fs 讀寫,把你的vps做成cloudfunction環境(小程式碎化片前後端支援環境)和配備一個雲IDE開發環境

在前面《利用onemanager配合公有云做站和nas》,《利用fodi給onemanager前後端分離》我們講到騰訊cloudbase和scf,提到它的後端管理頁面有一個線上IDE。類似內建的簡化版vscode online,在《在雲主機上安裝vscodeonline》,《panel.sh:一個nginx+docker的雲函和線上IDE面板,發明你自己的paas》我們講到自己構建這種online ide+serverless的paas面板技術,現在我們考慮聯接起containerd+vscode,打通這二者模擬cloudbase碎片化程式設計開發部署環境的設想:

我們知道serverless技術背後是容器跑起來的。一般地,dockerd+docker-cli主要被設計成stateless and immutable only as 自動化程式碼部署工具(部署和生成分離,CI/CD分離),docker維護一個image build in a place,then in another place的image pull -> image deploy -> container run的cycle(runc還有一個類似程序管理的task命令)是一個以registry為中心的pull->build->deploy迴圈流程。這就導致了一些問題:

如果不作特別說明,本文說到的docker/containerd可以混淆理解,containerd和docker都是容器執行時,docker最早是LXC(Linux Container)的二次封裝發行,後來使用的是Libcontainer技術,從1.11開始進一步演化為 runC 和 containerd,其利用的也是Linux核心特性namespaces(名稱空間) 、 cgroups (控制組)和AUFS(最新的overlay2)等技術,是作業系統層面的虛擬化技術,containerd重點是繼承在大規模的系統中,例如kubernetes,而不是面向開發者,讓開發者使用,更多的是容器執行時的概念,承載容器執行。docker偏指docker官方釋出的那個執行時版本和dockercli工具。

1)docker aufs/overlayfs被設計成只讀,和不變immutable環境(執行df可以看到其掛載到主機上的overlayfs mount,就像tc下可以看到tce用的loop一樣,類似/run/containerd/io.containerd.runtime.v2.task/default/xxxx/rootfs),裡面的檔案系統雖然是掛載到主機上的但不推薦直接讀寫因為它會破壞容器元資料,,要修改docker的檔案,我們要藉助docker提供的命令,進入具體docker容器空間按它正常的命令邏輯,去獲得檔案系統檢視(進入Docker容器比較常見的幾種做法如下:1.使用docker attach,2.使用SSH,3.使用nsenter,4.使用exec)後操作,之後docker cli(或其它定製的高階cli如k8s runctr)和dockerd管理器會保證整個應用庫中的(/var/lib/docker/image/xxx)的容器元資料不受破壞。------ 但是雖然容器可用這種方法編輯內容,其結果也是不能儲存的:下次重啟容器,還是會重新進入到這個以pull image開始的迴圈,除非你重新生成映象或提交容器即時更改。如上所述docker是部署和生成分離,docker中執行映象和生成映象這往往是一個離線操作 ------ 對於提交即時更改,docker有一個docker commit命令就是幹這事的(類似git commit和去主機快照技術),但除非用於特定目的(比如儲存現場,比如對於某些容器每次我們只需修改/home/app下的東西並commit,這二種場景下,其實也沒什麼問題。)這也是不受推薦的,因為它會導致複雜的映象:首先,如果在安裝軟體,編譯構建,那會有大量的無關內容被新增進來,如果不小心清理,將會導致映象及其臃腫。此外,使用docker commit 意味著所有對映象的操作都是黑箱操作,生成的映象也被稱為黑箱映象,換句話說,就是除了制定映象的人知道執行過什麼命令,怎麼生成的映象,別人根本無從得知,。換言之,docker fs並不是一個flat fs結構,不能按照普通的檔案系統的邏輯直接獲得路徑並讀寫。docker instant commit雖然受支援但也是不受推薦的,

  1. 你也完全可以將需要變動的部分放在外部,然後mount進容器,這個問題的本質其實是打通宿主機和容器中的某些通道,類似我們使用虛擬機器時在宿主和虛擬機器內共享一個volume或資料夾,類似遠端桌面將讀寫通道重定向到你本機(實際上是下載,對於遠端桌面所在伺服器是上傳),我們將host上可寫的資料被作為volume mount(或mount bind)進來到容器,將更改和變動只儲存在宿主端容器動態持續CI/CD即可,但是其有許可權處理問題(rootless containers...),我們知道這二套空間的使用者和檔案都不一樣。即我們把可以把裝有app的那個/home/app獨立出來放到主機。不內建入映象。containerd層只維護一個到主機相關目錄的索引,----- Docker itself does not seem to encourage using host-mounted writable volumes. 共享volume不受推薦。

其實docker的應用就是被設計作為執行空間,在很多文章中我們都講到不要將其作為存放任何資料空間,一是因為它用了奇怪的fs,二是因為它與宿主機share data也是非常不直觀的,見《10 ways to avoid in using docker》。。但是這是一種剛需:應用一般都是存在可讀寫的部分和可變存放資料的部分的,如何讓docker組成一個普通的符合可讀寫的應用環境呢?那麼程式上可以做到類似效果嗎或有改進方案嗎?

如果能做到或能改進,那麼在openfaas+vscode上,我們將這二者結合打通,形成cloudbase後端管理頁面程式碼編輯器編輯儲存類似的效果,這樣我們就可以不依賴openfaas-cli式以registry為中心的那個迴圈流程(請無視local registry搭建技術,因為它也是以registry為中心的),直接在online ide或web後臺管理中,即時地修改程式碼(這種思路是在經典流程中插入一個commit過程繞過registry 作inplace build using cache,最後按需push deploy迴歸到經典流程-這步不是必要的)。

buildkit似乎可以完成這個工作。

docker commit方案

buildkit是moby工程的一部分,它是從原始碼構建到容器的統一構建工具,在buildkit這類工具之前,業界都是用Docker in docker來處理的,後來轉為buildkit這類專門工具,它支援多種docker執行時和docker-cli構建時,你可以把buildkit它想象成docker compose的一種上層工具,以達到docker file format無關構建和麵向部署各種後端構建(處在中間層插入一個層,正符合我們上述提到的繞過registry,inplace building不push的要求)。即,它是一種統一面向容器的統一CI/CD工具,談到CI/CD,就是你在各種CI工具和其它提供商產品後臺修改git原始碼容器自動實時構建時那一大塊黑色輸出框背後用的那一套工具對應的輸出介面。因為它有分散式cache構建(傳統的Docker build cache only works on the same host),並行構建,統一中間格式這些。面向多種前端和多種後端,(buildkitd daemon支援runc後端和containerd後端,By default, the OCI (runc) worker is used. You can set --oci-worker=false --containerd-worker=true to use the containerd worker.你也可以用檔案定製它的引數https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md,它是幹什麼用的呢。將執行時和構建時像dockerd/dock-cli一樣形成cs結構,通過 http 通訊的方式執行構建。)openfass build就使用了buildkit,gitlab也有一個buildkite,都是統一容器CI/CD工具鏈上的東西。

moby就是我們前文《hyperkit:一個full codeable,full dev support的devops及cloud appmodel》介紹hyperkit的時候那家廠商造的,就是docker那家廠商。裡面的好多產品,都是docker轉正前的試驗品。包括moby,和hyperkit等。在《群暉+DOCKER,一個更好的DEVOPS+WEBOS雲平臺及綜合雲OS選型》我們談到用docker構建原生webos,cloudos,在《一種混合包管理和容器管理方案,及在tinycorelinux上安裝containerd和openfaas》我們又一次提到這個觀點,據說,用docker構建OS的還真不少。除了coreos還有moby。darch這種,Docker 在DockerCon 2017大會上釋出了一個自己的作業系統,宣稱LinuxKit,就是kernel+busybox實現的一個微縮linux系統,其中直接安裝了containerd和runc服務。其他服務全部都使用容器啟動。

前面說到docker偏指docker官方釋出的那個執行時版本和dockercli工具和規範,所以oci偏指containerd/runc這樣的工具和規範。buildkit都支援它們作為前後端。Open Container Initiative(OCI)目前有2個標準:runtime-spec以及image-spec。OCI(當前)相當於規定了容器的images和runtime的協議,只要實現了OCI的容器就可以實現其相容性和可移植性。那麼它與我們要達到的效果"達到類似web端編輯迅速ci/cd的類似目的嗎?不經過registry,就地構建並儲存結果給openfaas執行"有什麼關係呢?因為buildkit可以以containerd image store直接為後端(containerd image store,The containerd worker needs to be used)。形如:sudo buildctl build --frontend dockerfile.v0 --local context=./ --local dockerfile=./ --output type=image,name=docker.io/minlearn/dafsdf:latest(ctr --namespace=buildkit images ls,To change the containerd namespace, you need to change worker.containerd.namespace in /etc/buildkit/buildkitd.toml)

buildkit能達到以上效果主要是它掩蓋和封裝了snapshot和image操作這些中間過程,提供了一個類似docker commit的操作,可以生成含上述oci檔案的映象包,類似直接在映象上commit,要重新生成映象,這就不得不談到容器的snapshot。類似雲主機快照的熱備原理,ctr 也支援snapshot 操作,比如ctr contained 命令列工具 prepare 命令,基於指定的已提交態快照作為"父"建立一個新快照。還有commit,containerd是用snapshootter來完成這事的,Snapshotter is one of these plugins, which is used for storing extracted layers. During pulling an image, containerd extracts layers in the image and overlays them for preparing rootfs views called “snapshots” in the snapshotter. When containerd starts a container, it queries a snapshot to snapshotter and uses it as the container’s rootfs.基本上在本地重新生成映象這個過程應該是這樣的:1,Use the diff service to create a blob in content store with the committed snapshot 2,Create image config and distribution manifest https://github.com/opencontainers/image-spec in the content store that reference that layer blob 3,Set either the image config or manifest as a target descriptor for containerd image,它涉及到具體容器和映象的配置檔案定製都較繁瑣(oci維護一個映象和容器規範,你可以用ctr oci spec > /etc/containerd/cri-base.json獲得這些基礎配置。),因為新THE CONTAINER LAYERS和新THE IMAGE LAYERS層處理都很複雜。---- 所以我們得藉助buildkit這樣的工具。

除此之外,你還可以直接push到registry,sudo buildctl build --frontend dockerfile.v0 --local context=./ --local dockerfile=./ --output type=image,name=docker.io/minlearn/dafsdf:latest,push=true
還可以匯出為一個OCI tarball
buildctl build ... --output type=oci,dest=path/to/output.tar
buildctl build ... --output type=oci > output.tar

對docker OCI runtime對它的理解可以解決前面一個沒有解決的問題(ramdisk containerd下不能pivot root的問題,但是ramdisk chroot本身就是不受推薦的所以略過)。

甚至可以匯出構建中使用的cache,匯出快取對運營registry的ci/cd服務商非常有用。因為可以加速構建過程。

docker mount方案

上面談完了skip registry build的docker commit方案,對於mount方案,其實這個似乎更接近cloudbase那套(我們注意到cloudbash那個儲存後提交也有一定時間延遲的,但是延遲很小,可能並不是在提交併building。),這個就是層的概念。docker本身就支援mount主機上的目錄並run,buildkit在構建過程中就直接支援Buildkit allows secrets files to be mounted as a part of the build process. These secrets are kept in-memory and not stored within the image:

RUN --mount=type=bind (the default mount type)
This mount type allows binding directories (read-only) in the context or in an image to the build container.
RUN --mount=type=cache
This mount type allows the build container to cache directories for compilers and package managers.

但這些是buildkit是在構建時的mount,與執行時的mount要分開。對volume的定義是可以寫在oci runtime-spec配置檔案中的。

Volume Permission and Ownership

Volume permissions van be changed by configuring the ownership within the Dockerfile.You can, like Docker, mount volumes from the host to the container. The following mounts /home/Documents/images from the host to the container at /mnt/images with read and write permissions:

"mounts": [
    {
        "destination": "/mnt/images",
        "type": "bind",
        "source": "/home/Documents/images",
        "options": [
            "rbind",
            "rw"
        ]
    }
]

Remember that the UID:GID pair is relative to the user namespace that the user is going to run the container with. 

這種方案下,只要類似openfaas gateway:8080/ui或cloudbase後臺支援,可以完全分離docker構建/執行時所用的容器本身映象和外來資料。在資料發生變動時,以手動提交方式或自動方式觸發一次構建。


具體到將上述猜想做到openfaas+vscodelonline產品和將這個功能放進管理後端中,這實際上是處理openfaas後端如何呼叫openfaas-cli工具(通過json api)這類問題和https://code.visualstudio.com/docs/remote/containers-advanced所提到的那些問題。除此之外,新的加了edit按鈕的後端,必須使得每個容器被depoly時,faas自動為其匯出資料目錄,不必寫明在docker-composer.yml中最好。