1. 程式人生 > >給一個正在執行的Docker容器動態新增Volume

給一個正在執行的Docker容器動態新增Volume

之前有人問我Docker容器啟動之後還能否再掛載卷,考慮到mnt名稱空間的工作原理,我一開始認為這很難實現。不過現在Petazzoni通過使用nsenter和繫結掛載實現了這個需求,你可以在你的環境中測試下。

之前有人問我Docker容器啟動之後還能否再掛載卷,考慮mnt名稱空間的工作原理,我一開始認為這很難實現。不過現在我認為是它實現的。

簡單來說,要想將磁碟卷掛載到正在執行的容器上,我們需要:
  • 使用nsenter將包含這個磁碟卷的整個檔案系統mount到臨時掛載點上;
  • 從我們想當作磁碟卷使用的特定資料夾中建立繫結掛載(bind mount)到這個磁碟卷的位置;
  • umount第一步建立的臨時掛載點。

注意事項

在下面的示例中,我故意包含了$符號來表示這是Shell命令列提示符,以幫助大家區分哪些是你需要輸入的,哪些是機器回覆的。有一些多行命令,我也繼續用>。我知道這樣使得例子裡的命令無法輕易得被拷貝貼上。如果你想要拷貝貼上程式碼,請檢視文章最後的示例指令碼。

詳細步驟

下面示例的前提是你已經使用如下命令啟動了一個簡單的名為charlie的容器:
$ docker run --name charlie -ti ubuntu bash

我們需要做的是將宿主資料夾/home/jpetazzo/Work/DOCKER/docker掛載到容器裡的/src目錄。好了,讓我們開始吧。

nsenter

首先,我們需要nsenter以及 docker-enter幫助指令碼。為什麼?因為我們要從容器中mount檔案系統。由於安全性的考慮,容器不允許我們這麼做。使用nsenter,我們可以突破上述安全限制,在容器的上下文(嚴格地說,是名稱空間)中執行任意命令。當然,這必須要求擁有Docker宿主機的root許可權。
nsenter
最簡單的安裝方式是和docker-enter指令碼關聯執行:
$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter

更多細節,請檢視nsenter專案主頁。

找到檔案系統

我們想要在容器裡掛載包含宿主資料夾(/home/jpetazzo/Work/DOCKER/docker)的檔案系統。那我們就需要找出哪個檔案系統包含這個目錄。

首先,我們需要canonicalize(或者解除引用)檔案,以防這是一個符號連結,或者它的路徑包含符號連結:
$ readlink --canonicalize /home/jpetazzo/Work/DOCKER/docker
/home/jpetazzo/go/src/github.com/docker/docker

哈,這的確是一個符號連結!讓我們將其放入一個環境變數中:
$ HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
$ REALPATH=$(readlink --canonicalize $HOSTPATH)

接下來,我們需要找出哪個檔案系統包含這個路徑。我們使用一個有點讓人意想不到的工具來做,它就是df:
$ df $REALPATH
Filesystem     1K-blocks      Used Available Use% Mounted on
/sda2          245115308 156692700  86157700  65% /home/jpetazzo

使用-P引數(強制使用POSIX格式,以防是exotic df,或者是其他人在Solaris或者BSD系統上裝Docker時執行的df),將結果也放到一個變數裡:
$ FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

找到檔案系統的裝置(和sub-root)

現在,系統裡已經沒有繫結掛載(bind mounts)和BTRFS子捲了,我們僅僅需要檢視/proc/mounts,找到對應於/home/jpetazzo檔案系統的裝置就可以了。但是在我的系統裡,/home/jpetazzo是BTRFS池的子卷,要想得到子卷的資訊(或者bind mount資訊),需要檢視/proc/self/moutinfo。

如果你從來沒有聽說過mountinfo,可以檢視核心文件的proc.txt

首先,得到檔案系統裝置資訊:
$ while read DEV MOUNT JUNK
> do [ $MOUNT = $FILESYS ] && break
> done </proc/mounts
$ echo $DEV
/dev/sda2

接下來,得到sub-root資訊(比如,已掛載檔案系統的路徑):
$ while read A B C SUBROOT MOUNT JUNK
> do [ $MOUNT = $FILESYS ] && break
> done < /proc/self/mountinfo 
$ echo $SUBROOT
/jpetazzo

很好。現在我們知道需要掛載/dev/sda2。在檔案系統內部,進入/jpetazzo,從這裡可以得到到所需檔案的剩餘路徑(示例中是/go/src/github.com/docker/docker)。
讓我們計算出剩餘路徑:
$ SUBPATH=$(echo $REALPATH | sed s,^$FILESYS,,)

注意:這個方法只適用於路徑裡沒有符號“,”的。如果你的路徑裡有“,”並且想使用本文方法掛載目錄,請告訴我。(我需要呼叫Shell Triad來解決這個問題:jessiesoulshaketianon?)
在進入容器之前最後需要做的是找到這個塊裝置的主和次裝置號。可以使用stat:
$ stat --format "%t %T" $DEV
8 2

注意這兩個數字是十六進位制的,我們之後需要的是二進位制。可以這麼轉換:
$ DEVDEC=$(printf "%d %d" $(stat --format "0x%t 0x%T" $DEV))

總結

還有最後一步。因為某些我無法解釋的原因,一些檔案系統(包括BTRFS)在掛載多次之後會更新/proc/mounts裡面的裝置欄位。也就是說,如果我們在容器裡建立了名為/tmpblkdev的臨時塊裝置,並用其掛載我們自己的檔案系統,那麼檔案系統(在宿主機器裡!)會顯示為/tmpblkdev,而不是/dev/sda2。這聽起來無所謂,但實際上這會讓之後試圖得到檔案系統塊裝置的操作都失敗。

長話短說,我們想要確保塊裝置節點在容器裡位於和宿主機器上的同一個路徑下。

需要這麼做:
$ docker-enter charlie -- sh -c \
> "[ -b $DEV ] || mknod --mode 0600 $DEV b $DEVDEC"

建立臨時掛載點掛載檔案系統:
$ docker-enter charlie -- mkdir /tmpmnt
$ docker-enter charlie -- mount $DEV /tmpmnt

確保卷掛載點存在,bind mount卷:
$ docker-enter charlie -- mkdir -p /src
$ docker-enter charlie -- mount -o bind /tmpmnt/$SUBROOT/$SUBPATH /src

刪除臨時掛載點:
$ docker-enter charlie -- umount /tmpmnt
$ docker-enter charlie -- rmdir /tmpmnt

(我們並不清除裝置節點。一開始就檢查裝置是否存在可能有點多餘,但是現在再檢查就已經很複雜了。)

大功告成!

讓一切自動化

下面這段可以直接拷貝貼上了。
#!/bin/sh
set -e
CONTAINER=charlie
HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
CONTPATH=/src

REALPATH=$(readlink --canonicalize $HOSTPATH)
FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

while read DEV MOUNT JUNK
do [ $MOUNT = $FILESYS ] && break 
done  </proc/mounts
[ $MOUNT = $FILESYS ] # Sanity check!

\while read A B C SUBROOT MOUNT JUNK
\do [ $MOUNT = $FILESYS ] && break
\done < /proc/self/mountinfo 
[ $MOUNT = $FILESYS ] # Moar sanity check!

SUBPATH=$(echo $REALPATH | sed s,^$FILESYS,,)
DEVDEC=$(printf "%d %d" $(stat --format "0x%t 0x%T" $DEV))

docker-enter $CONTAINER -- sh -c \
     "[ -b $DEV ] || mknod --mode 0600 $DEV b $DEVDEC"
docker-enter $CONTAINER -- mkdir /tmpmnt
docker-enter $CONTAINER -- mount $DEV /tmpmnt
docker-enter $CONTAINER -- mkdir -p $CONTPATH
docker-enter $CONTAINER -- mount -o bind /tmpmnt/$SUBROOT/$SUBPATH $CONTPATH
docker-enter $CONTAINER -- umount /tmpmnt
docker-enter $CONTAINER -- rmdir /tmpmnt

狀態和限制

上述方法不適用於不基於塊裝置的檔案系統,只有在/proc/mounts能正確得到塊裝置節點(上面談到,並不總是能正確得到)的時候才能起作用。另外,我只測試了我自己的環境,沒有在雲實例之類的環境裡測試過,但是我很想知道在那裡是否適用。

相關推薦

一個正在執行Docker容器動態新增Volume

之前有人問我Docker容器啟動之後還能否再掛載卷,考慮到mnt名稱空間的工作原理,我一開始認為這很難實現。不過現在Petazzoni通過使用nsenter和繫結掛載實現了這個需求,你可以在你的環境中測試下。 之前有人問我Docker容器啟動之後還能否再掛載卷,考慮mnt名

Docker 容器新增資料卷的2種方式

文章目錄 1、容器資料卷是什麼? 2、`容器內` 新增資料卷的2種方式 3、直接命令新增 3.1、命令 3.2、檢視資料卷是否掛載成功 3.3、容器和宿主機之間資料共享 3.3、容器停止退出後,主機修改後資料是否同步

如何打包建立映象和執行Docker容器及常用命令

如何寫Dockerfile參考: 相關主機和地址 主要命令 檢視本地已有映象 docker images 如: 10.10.36.213:root@docker-registry:/root/Dockerfile/jetty/jetty-1.8.45]# d

在 CentOS 中執行 Docker 容器中的 Redis 映象

1. 載入映象 太容易了,就一行命令,下載映象、執行映象都一起完成了。 docker run -p 6379:6379 -v $PWD/data:/data -d redis:3.2 redis-

ASP.NET Core 如何在執行Docker容器時指定容器外部埠

前面我寫了一系列關於持續整合的文章,最終構建出來的映象執行之後,應該會發現每次構建執行之後埠都變了,這對於我們來說是十分不方便的,所以我們可以通過修改docker compose的配置檔案來完成我們的需求。 熟悉Docker的都應該知道容器執行時其內部會有一個埠以對映到我們外部的埠,我們需要固定的就是這個外部

一個整數n,求出位數。並按序輸出,逆序輸出

求出位數通過讓給定的正整數n整除10,且每整除一次讓統計位數的變數count自增一,返回count得到位數。#include<stdio.h> int GetFigure(int n) { int count=0; do { count ++;

VC++ 如何向一個列表控制元件中動態新增複選框

宣告:由於自己的程式設計需要,這裡的列表控制元件只有一列,是list樣式。 1.首先給列表控制元件關聯一個成員變數,例如: DDX_Control(pDX, IDC_LIST_DtvParent, m

一個整數n,計算從1-n中出現1的次數

con 個數字 優化 個數 數字 higher 通過 需要 出現的次數 如12出現1的次數為5,分別是:1,10,11,12 一般做法:從1-n遍歷,計算每一個數中每一位出現1的次數 function count(num){ var n=0;

執行中的Docker容器新增埠對映

問題:如何對執行中的Docker容器新增埠對映? 解決方案如下: iptables -t nat -A DOCKER -p tcp --dport ${YOURPORT} -j DNAT --to-destination ${CONTAINERIP}:${YOUR

Docker入門系列之一:在一個Docker容器執行指定的web應用

實現題目描述的這個需求有很多種辦法,作為入門,讓我們從最簡單的辦法開始。 首先使用命令docker ps確保當前沒有正在執行的Docker例項。 執行命令docker run -it nginx: 然後我們另外開一個終端,用docker ps命令檢視這個執行起來的容器例項,Status

java圖片新增文字水印,以及docker容器新增中文字型支援

發現雖然簡單,但是水印內容通過編碼拼接在圖片地址後面,每次訪問都要加上,如果要儲存下來還需要自己存第二遍有水印的圖。可是我們並不需要沒有水印的原圖,於是決定通過程式碼新增,更自由。 程式碼如下: /** * 新增文字水印 * @param inpu

從零開始通過idea外掛將一個spring boot專案部署到docker容器執行

實操:將一個spring boot專案部署到docker容器裡執行 實驗需要的環境: 騰訊雲+Ubuntu 16.04 x64+idea+外掛docker integration+daocloud 第一步,安裝作業系統 首先在騰訊雲上安裝一個乾淨

一個Docker容器執行指定的web應用

實現題目描述的這個需求有很多種辦法,作為入門,讓我們從最簡單的辦法開始。 首先使用命令docker ps確保當前沒有正在執行的Docker例項。 執行命令docker run -it nginx: 然後我們另外開一個終端,用docker ps命令檢視這個執行起來的容器例項,Status

Docker容器中如何執行一個帶GUI的app?

問:How can you run GUI apps in a docker container

如何避免Docker容器啟動指令碼執行後自動退出——一個cron定時任務docker映象方案

近期想做一個cron定時任務的docker,在Dockerfile中做如下定義 FROM library/alpine:latest RUN apk --update add rsync openssh bash VOLUME ["/data"] ADD start.sh

Docker容器執行一個Apache伺服器

1.在根目錄下建立一個test目錄,並編寫Dockerfile。 需在root使用者下進行操作 # sudo su - 切換成root使用者 # cd / 進入根目錄 # mkdir tes

php 中preg_replace執行一個則表達式的搜索和替換

cnblogs param subject bject placement family sub ica ans preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $lim

7,裝飾者模式(Decorator Pattern)動態一個對象添加一些額外的職責。就增加功能來說,此模式比生成子類更為靈活。繼承關系的一個替換方案。

做到 活性 splay .com 重新 裝飾 run play 情況 裝飾( Decorator )模式又叫做包裝模式。通過一種對客戶端透明的方式來擴展對象的功能,是繼承關系的一個替換方案。 裝飾模式就是把要添加的附加功能分別放在單獨的類中,並讓這個

運行第一個 Service - 每天5分鐘玩轉 Docker 容器技術(96)

教程 容器 docker swarm 上一節我們創建好了 Swarm 集群, 現在部署一個運行 httpd 鏡像的 service,執行如下命令:docker service create --name web_server httpd部署 service 的命令形式與運行容器的 docker

DOCKER 運行中的容器添加映射端口

docker端口映射DOCKER 給運行中的容器添加映射端口方法11、獲得容器IP將container_name 換成實際環境中的容器名docker inspect `container_name` | grep IPAddress12、 iptable轉發端口將容器的8000端口映射到docker主機的80