痛点

  • Windows中,Docker只能安装在虚拟机或者其他服务器上,如果要传输文件,只能构建镜像,或者借助共享文件夹
  • 在调试期间,经常修改,在Docker中遗留大量的镜像,事后清理的时候,需要回忆这个镜像是否会用到
  • Windows共享文件夹的方式不便于移植,即使使用脚本创建共享文件夹也需要手工操作,而且需要管理员权限
  • 很多时候只是传输配置文件,构建镜像显得太重了,共享文件夹就更重了,而且造成大量细碎的镜像
  • 尽管可以在docker-compose.yml文件中运行脚本,不过需要处理转义,而且排版也不方便,传输脚本上去不会有转义问题,例如$美元符号在docker-compose.yml中会被识别为变量,同时单独写的脚本文件也适合阅读,只需要在镜像中调试成功后,既可以使用本文介绍的方式执行

向Docker共享文件夹的另一个轮子请访问

https://github.com/huzhenghui/share-windows-folder-to-docker

开包即用,不用看代码

下载项目

git clone https://github.com/huzhenghui/transfer-file-to-docker-by-env-file

代码结构

│  generate-env-file.ps1
│  LICENSE
│
├─sample
│      transfer-file-to-docker-by-env-file.env
│      transfer-file-to-docker-by-env-file.yml
│
└─source
        sample1.txt
        sample2.txt
        sample3.txt

其中generate-env-file.ps1是脚本,source是示例源文件,sample是示例生成文件。

生成示例文件

PowerShell中进入项目文件夹,运行命令

.\generate-env-file.ps1 -sample

输出如下所示

extract script:

k=1
while true; do
  eval c=\$_T_${k}_C
  if [ -z $c ]; then
    exit;
  fi
  eval n=\$_T_${k}_N
  echo $c | base64 -d > /dev/shm/$n;
  k=`expr $k + 1`
done;

File Number :  1
File Name :  sample1.txt

File Number :  2
File Name :  sample2.txt

File Number :  3
File Name :  sample3.txt

env-file :  C:\Users\huzh\OneDrive\Docker\transfer-file-to-docker-by-env-file\transfer-file-to-docker-by-env-file.env

Sample Docker Compose File : C:\Users\huzh\OneDrive\Docker\transfer-file-to-docker-by-env-file\transfer-file-to-docker-by-env-file.yml

Usage : docker-compose -f C:\Users\huzh\OneDrive\Docker\transfer-file-to-docker-by-env-file\transfer-file-to-docker-by-env-file.yml up

其中extract script是用于解析环境变量到文件的脚本,这个脚本预制在generate-env-file.ps1文件中,这个脚本本身也是借助环境变量传输到Docker执行。

接下来显示在source文件夹中搜索到的三个文件。

extract scriptsource都生成在下面的env-file文件中。

本例中使用-sample参数,同时生成了Docker Compose示例文件,路径在Sample Docker Compose File中。

运行示例文件

Usage为示例文件的使用方式,运行

docker-compose -f C:\Users\huzh\OneDrive\Docker\transfer-file-to-docker-by-env-file\transfer-file-to-docker-by-env-file.yml up

可以看到输出结果为

Starting transferfiletodockerbyenvfile_sample_1 ... done
Attaching to transferfiletodockerbyenvfile_sample_1
sample_1  | export HOME='/root'
sample_1  | export HOSTNAME='ef5ce5fc2149'
sample_1  | export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
sample_1  | export PWD='/'
sample_1  | export SHLVL='1'
sample_1  | export _T_0_C='az0xCndoaWxlIHRydWU7IGRvCiAgZXZhbCBjPVwkX1RfJHtrfV9DCiAgaWYgWyAteiAkYyBdOyB0aGVuCiAgICBleGl0OwogIGZpCiAgZXZhbCBuPVwkX1RfJHtrfV9OCiAgZWNobyAkYyB8IGJhc2U2NCAtZCA+IC9kZXYvc2htLyRuOwogIGs9YGV4cHIgJGsgKyAxYApkb25lOw=='
sample_1  | export _T_1_C='c2FtcGxlMQo='
sample_1  | export _T_1_N='sample1.txt'
sample_1  | export _T_2_C='c2FtcGxlMg0Kc2FtcGxlMg0K'
sample_1  | export _T_2_N='sample2.txt'
sample_1  | export _T_3_C='c2FtcGxlMw0Kc2FtcGxlMw0Kc2FtcGxlMw0K'
sample_1  | export _T_3_N='sample3.txt'
sample_1  | total 12
sample_1  | drwxrwxrwt    2 root     root           100 Mar 14 16:59 .
sample_1  | drwxr-xr-x    5 root     root           340 Mar 14 16:59 ..
sample_1  | -rw-r--r--    1 root     root             8 Mar 14 16:59 sample1.txt
sample_1  | -rw-r--r--    1 root     root            18 Mar 14 16:59 sample2.txt
sample_1  | -rw-r--r--    1 root     root            27 Mar 14 16:59 sample3.txt
transferfiletodockerbyenvfile_sample_1 exited with code 0

可以看到前述的extract scriptsource都通过环境变量传输到了docker中,下面列出了传输后的文件。

示例文件解析

查看示例文件可以看到

version: "3"
  services:
    sample:
      image: alpine
      env_file:
        - transfer-file-to-docker-by-env-file.env
      entrypoint: |
        /bin/sh
        -c
        "export;
        echo $$_T_0_C | base64 -d | /bin/sh;
        ls -al /dev/shm"

其中env_file为引用环境变量

env_file:
  - transfer-file-to-docker-by-env-file.env

entrypoint中为示例脚本,export为输出环境变量,仅用于演示和调试,实际项目不需要。

在实际项目中需要使用如下命令

echo $$_T_0_C | base64 -d | /bin/sh

也就是还原保存在环境变量_T_0_C中的脚本,并运行。阅读这行命令会发现使用了两个美元符号$$
这是因为美元符号$即是Shell脚本中的变量前缀,也是Docker Compose文件中的变量前缀,
为了避免Docker Compose转义,就需要使用两个美元符号$$,这也是制作这个轮子的初衷之一。

最后一行是显示生成的文件,仅用于演示和调试,实际项目不需要。

ls -al /dev/shm

本例中为了速度以及不影响后续的容器操作,把文件直接放在内存文件系统中,
考虑到使用场景主要是小文件,内存文件系统足够用了。

参数说明

-source,指定源文件路径

如果不指定的话,自动为当前路径下的source路径

-target,指定目标文件路径

如果不指定的话,自动为当前路径下的transfer-file-to-docker-by-env-file.env路径

-sample,是否生成示例Docker Compose文件

如果不设置这个参数,则仅生成.env文件,用于使用脚本自动生成。

源代码解析

源代码很简单,简单说明一下

声明参数

Param(
  [string]$source,
  [string]$target,
  [switch]$sample
)

处理source参数,源文件路径

if ([string]::IsNullOrEmpty($source))
{
  $source = -Join((Get-Location).Path, '\source')
}
$sourcePath = (Resolve-Path $source).Path
if ([string]::IsNullOrEmpty($sourcePath))
{
  echo "source is not exists: $source"
  exit
}

处理target参数,目标文件路径

if ([string]::IsNullOrEmpty($target))
{
  $target = -Join((Get-Location).Path, '\transfer-file-to-docker-by-env-file.env')
}

内置的解析脚本

$extract=@'
k=1
while true; do
  eval c=\$_T_${k}_C
  if [ -z $c ]; then
    exit;
  fi
  eval n=\$_T_${k}_N
  echo $c | base64 -d > /dev/shm/$n;
  k=`expr $k + 1`
done;
'@
Write-Host 'extract script:'
Write-Host ''
Write-Host $extract

解析脚本不需要转义,因此使用单引号'

输出解析脚本到环境变量文件

Write-Output _T_0_C=$([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($extract.Replace("`r`n", "`n")))) |
Out-File $target -Encoding UTF8

先替换解析脚本中的换行符,从CRLF替换为LF,然后转换为字节数组,接下来使用base64转码,输出到环境变量文件中。

输出源文件到环境变量中

$k=1
Get-ChildItem $sourcePath -File |
ForEach-Object -Process{
  Write-Host ''
  Write-Host 'File Number : ' $k
  Write-Host 'File Name : ' $_.Name
  Write-Output _T_${k}_N=$_
  Write-Output _T_${k}_C=$([Convert]::ToBase64String([System.IO.File]::ReadAllBytes($_.FullName)))
  $k=$k+1
} |
Out-File $target -Append -Encoding UTF8
Write-Host ''
Write-Host 'env-file : ' $target

遍历源文件路径,把源文件名和源文件内容分别保存在环境变量中,源文件内容按照字节数组读取,然后使用base64转码。

显示示例Docker Compose文件

if($sample.isPresent)
{
  $sampleCompose=@"
  version: `"3`"
  services:
    sample:
      image: alpine
      env_file:
        - $(([system.io.fileinfo]$target).Name)
      entrypoint: |
        /bin/sh
        -c
        `"export;
        echo `$`$_T_0_C | base64 -d | /bin/sh;
        ls -al /dev/shm`"
"@
  Write-Host ''
  $sampleYmlFile = -Join(([system.io.fileinfo]$target).DirectoryName, '\', ([system.io.fileinfo]$target).BaseName, '.yml')
  Write-Output $sampleCompose | Out-File $sampleYmlFile -Encoding UTF8
  Write-Host 'Sample Docker Compose File :' $sampleYmlFile
  Write-Host ''
  Write-Host "Usage : docker-compose -f $sampleYmlFile up"
}

仅在设置-sample参数时执行,由于示例文件中需要指定环境变量文件的路径,因此需要使用双引号",因而其中的双引号和美元符号$都需要转义。