前面两篇(简单运维1简单运维2)介绍了一些Windows Server Docker相关的基本运维知识。今天这一篇,Windows Server Dockerfile葵花宝典,涵盖了许多典型场景的Windows Server下的Dockerfile实例,并且每一个都包含可直接运行的代码实例,完全开源,并且新示例持续添加中。也希望大家能一起贡献经验。

示例源码

Github Repo: windows-dockerfile-lab

所有示例均经过Windows Server 2016环境实际测试。如果你需要了解如何配置一个Windows Server Docker的测试环境,可以参考本系列的第一篇

咱们从一些基础的Dockerfile命令说起:

ENTRYPOINT和CMD

源码:entrypoint_and_cmd

FROM microsoft/windowsservercore

#usually, ENTRYPOINT should be fixed and CMD to be overridden when necessary
#always use the exec form instead of shell mode for ENTRYPOINT and CMD commands and params ENTRYPOINT ["ping", "-n", "3"]
CMD ["baidu.com"] #to build, execute: docker build -t entrypoint_and_cmd .
#to override CMD, execute: docker run --rm entrypoint_and_cmd 360.cn

每一个Dockerfile必须的两个命令是FROM命令,和ENTRYPOINT/CMD。FROM命令无需多说,就是指定当前docker image的爹是谁。这里重点说说,ENTRYPOINT和CMD命令。ENTRYPOINT和CMD都可以用来指定docker容器实例启动时执行的启动程序,这两个命令可以分别使用,但是一般推荐组合使用。

以上面的示例为例,这里组合使用了ENTRYPOINT和CMD,运行时的效果是什么呢?

如果我们在命令行执行docker run entrypoint_and_cmd启动这个docker容器,它就相当于执行了下面这一行命令:

ping -n 3 baidu.com

而如果我们用docker run entrypoint_and_cmd 360.cn启动这个容器,则相当于执行了下面这条命令:

ping -n 3 360.cn

也就是说,通过CMD指定的部分,在执行docker run时,如果在参数中的image名字后面指定了一些参数,这些参数会覆盖Dockerfile中定义的CMD后面的命令。

限于篇幅,本文中演示的示例,都是解决相同问题的最佳实践或者推荐实践。但是不会扩展讲太多为什么这是最佳实践,但是我会附上一些参考资料,感兴趣的朋友可以自行阅读。以上面的ENTRYPOINT和CMD的区别问题,更多延伸内容,请参考这篇文章:Dockerfile: ENTRYPOINT vs CMD

ADD和COPY

源码:add_and_copy

FROM microsoft/windowsservercore

#on windows, ADD & COPY almost the same

ADD test_folder /test_folder_add
COPY test_folder /test_folder_copy ADD test_folder /test_folder_add2
COPY test_folder /test_folder_copy2 #unlike on linux, ADD doesn't support uncompressing zip files on windows ADD test_folder.zip /
COPY test_folder.zip / ADD test_folder.zip /test_folder_add2.zip
COPY test_folder.zip /test_folder_copy2.zip #how to ADD or COPY files containing space in the name ADD ["file name with space.txt", "/"]
COPY ["file name with space.txt", "/file name with space copy.txt"] #overwriting files also works ADD test.txt /test_overwrite_add.txt
ADD test_overwrite_add.txt /test_overwrite_add.txt COPY test.txt /test_overwrite_copy.txt
COPY test_overwrite_copy.txt /test_overwrite_copy.txt #add files with wildchar also works ADD wildchar* /
RUN del wildchar*
COPY wildchar* / #the only obvious difference of ADD and COPY on windows is, you can use ADD for downloading files, but not COPY ADD https://github.com/jquery/jquery/archive/3.2.1.zip /
ADD https://github.com/jquery/jquery/archive/3.2.1.zip /3.2.1_2.zip ENTRYPOINT ["cmd", "/c", "dir"] #to build, execute: docker build -t add_and_copy .
#to run, execute: docker run --rm add_and_copy

ADD和COPY也是几乎每个Dockerfile都会用到的命令,它们的作用其实非常类似,就是将文件或者目录从docker client执行的机器复制到正在编译的docker image中。这两个命令只有一些细小差别:

  • ADD命令可用于从一个URL下载文件,而COPY命令不行;
  • 在Linux下的docker中,ADD命令还有一个特殊能力,就是如果ADD一个zip压缩包的话,docker build时它能做自动解压缩,但是在Windows Docker下,没有这个效果;

合并和减少RUN命令

源码:merge_and_reduce_run

FROM microsoft/windowsservercore

#run 2 commands in sequence even if the firts one fails, but not the second fails
RUN cd notexists & md folder1 #run 2 commands in sequence only if both succeed
RUN md folder2 && md folder3 #run 2 commands in sequence only if at least one succeeds
RUN md folder4 || cd notexists #if one line of RUN is too long, breakdown into multiple lines with \ at the end
#so that it is more friendly for code review
RUN echo 1 \
2 \
3 ENTRYPOINT ["cmd", "/c", "dir"] #to build, execute: docker build -t merge_and_reduce_run .
#to run CMD, execute: docker run --rm merge_and_reduce_run

在执行docker build的时候,我们一定能注意到它有step 1,step 2……每个step对于docker build来说,实际上就是创建一个临时的docker image,只执行了一个RUN,所以,如果我们不注意,将每个细小的步骤都写一个RUN的话,最后就会发现,我们的Dockerfile的build变得非常慢,因为步骤太多了。所以,我们应该合理地合并没必要分开RUN的命令。

但是,合并的两个命令到一个RUN里,其实没表面那么简单。以上面的示例为例,前面三个主要是说如何合并多个windows cmd命令:

  • &连接的两个命令代表即使前一个命令执行失败,只要后一个命令执行成功,这个RUN就不算失败;
  • &&连接的两个命令代表两个命令都必须执行成功,整个RUN才成功;
  • ||连接的两个命令代表只要任意一个执行成功,就算整个RUN成功;

注意,所谓RUN成功指的是,如果RUN失败的话,docker build就会中断执行。因此,我们要根据实际情况,只允许可以失败的命令失败,确保重要的命令必须成功。

当然,除了合并windowns cmd命令,我们也可以合并多个powershell命令到一个RUN,例如:

RUN powershell -command "command1;command2;command3"

或者,如果有比较复杂的多个命令,我们最好把多个命令写成一个.cmd或者.ps1脚本,这样,Dockerfile就只需要一个RUN了。

另外,对于一行RUN太长的情况,最好通过""分割成多行书写,这主要是为了方便做代码的Review,这样在diff工具里,能更清晰的显示到底改了什么参数。

到目前为止的sample,其实都不不算Windows Docker特定的案例,相比Linux下,其实都很类似,属于基础中的基础。下面,我们就开始介绍一些Windows下特有的案例:

unzip

源码:unzip

FROM microsoft/windowsservercore

COPY test_folder.zip /

#unzip
RUN powershell -Command "expand-archive -Path 'c:\test_folder.zip' -DestinationPath 'c:\'" ENTRYPOINT ["cmd", "/c", "dir"] #to build, execute: docker build -t unzip .
#to run CMD, execute: docker run --rm unzip

前面我们说到,ADD命令在Windows Docker下不支持解压缩zip文件。那么,在Windows下如何解压缩呢?最简单的方法,就是使用Expand-Archive这个powershell命令。

set_hosts

源码:set_hosts

FROM microsoft/iis

#install ASP.NET 4.5
RUN dism /online /enable-feature /all /featurename:IIS-ASPNET45 /NoRestart #deploy webapp
COPY iis-demo /inetpub/wwwroot/iis-demo
RUN /windows/system32/inetsrv/appcmd.exe add app /site.name:"Default Web Site" /path:"/iis-demo" /physicalPath:"c:\inetpub\wwwroot\iis-demo" #set entrypoint script
COPY scripts /scripts
ENTRYPOINT ["C:\\scripts\\SetHostsAndStartMonitoring.cmd"] #to build, execute: docker build -t set_hosts .
#to run, execute: docker run --rm --env-file ./hosts.env set_hosts

前面的文章中,我们提到过Windows下的Docker目前不支持docker run的--add-host参数。所以,这里我们提供了一个基于环境变量,这里用一个环境变量文件(./hosts.env ),设置Windows系统的hosts文件的方法。其中读取环境变量并设置hosts的代码,其实就是下面的powershell脚本:

If ($env:HOSTS) {
$hosts = $env:HOSTS.Replace(",", "`r`n");
$hosts | Set-Content "C:\Windows\System32\drivers\etc\hosts"
"Applied hosts: `r`n" + $hosts;
}

gacutil

源码:gacutil

FROM microsoft/windowsservercore

#copy minimized gacutil binary to container
COPY tools.zip /
RUN powershell -Command "expand-archive -Path 'c:\tools.zip' -DestinationPath 'c:\'" #install a DLL to GAC with gacutil
COPY Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll /
RUN /tools/gacutil.exe /i Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll ENTRYPOINT ["cmd", "/c", "/tools/gacutil.exe", "/l", "Microsoft.VisualStudio.QualityTools.UnitTestFramework"] #to build, execute: docker build -t gacutil .
#to run, execute: docker run --rm gacutil

gacutil是常用的通过命令行注册.NET DLL到GAC的工具。但是这个工具包含在.Net Framework SDK中,并不包含于.NET Framework的分发库中。而为了注册几个DLL而让docker容器里面安装一个臃肿的.NET SDK实在有点难受。因此,这个示例包含了从.NET Framework SDK中抽取出来的单独的gacutil工具,只有94k大小。

enable_eventlog

源码:enable_eventlog

FROM microsoft/windowsservercore

#enable eventlog
RUN powershell.exe -command Set-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\WMI\Autologger\EventLog-Application Start 1 # list latest 10 system eventlog
CMD powershell -command "Get-EventLog system -newest 10 | Format-List" #to build, execute: docker build -t enable_eventlog .
#to run, execute: docker run --rm enable_eventlog

这个太简单了,不多解释了,就是通过powershell设置了一个注册表值。

enable_wcf

源码:enable_wcf

FROM microsoft/iis

#install ASP.NET 4.5
RUN dism /online /enable-feature /all /featurename:IIS-ASPNET45 /NoRestart #install WCF features, here we only enabled http and tcp protocol
#for the full list of features you can enable, execute "dism /online /get-features" on a windows server machine
RUN dism /online /enable-feature /featurename:WCF-HTTP-Activation45 /all
RUN dism /online /enable-feature /featurename:WCF-TCP-Activation45 /all #enable WCF protocols
RUN /windows/system32/inetsrv/appcmd.exe set config -section:system.applicationHost/sites /[@0].[@0].enabledProtocols:"http,net.tcp" ENTRYPOINT ["c:\\ServiceMonitor.exe", "w3svc"] #to build, execute: docker build -t enable_wcf .
#to run, execute: docker run --rm enable_wcf

在远古的SOA时代(好像就在眼前,悲伤),WCF是.NET下最热门的技术之一,现在是江河日下了。不过,如何通过命令行enable WCF并设置IIS里的protocols呢?

另外,提一下,上面这行ENTRYPOINT ["c:\ServiceMonitor.exe", "w3svc"]其实不是必须的,因为microsoft/iis这个image其实默认就是执行的这个ENTRYPOINT。这其实是在监控w3svc这个Windows Service的运行,我们当然也可以用它来监控我们自己的Windows Service的,这里顺便提一下。

set_iis_ssl

源码:set_iis_ssl

FROM microsoft/iis

#install ASP.NET 4.5
RUN dism /online /enable-feature /all /featurename:IIS-ASPNET45 /NoRestart #setup SSL in IIS
ADD iis-test.pfx \iis-test.pfx
ADD SetSSL.ps1 \SetSSL.ps1 #set entrypoint script
ADD SetSSLAndStart.cmd \SetSSLAndStart.cmd
ENTRYPOINT ["C:\\SetSSLAndStart.cmd"] #to build, execute: docker build -t set_iis_ssl .
#to run, execute: docker run --rm -e "SSL_PASS=test" -p 443:443 set_iis_ssl

通过命令行设置IIS的SSL,这个可能干过的不多,但现在SSL几乎是大多数主流网站的标配了,在docker容器里部署网站,也是必不可少的。其中,主要的逻辑,在SetSSLAndStart.cmd中调用SetSSL.ps1执行:

cmd /c "certutil -p %SSL_PASS% -importPFX c:\iis-test.pfx"
$addr=[System.Net.Dns]::GetHostAddresses([System.Environment]::MachineName).IPAddressToString
$env:ssl_addr=$addr[1]+":443"
cmd /c 'netsh http add sslcert ipport=%ssl_addr% certstorename=MY certhash=C97E4D29C00D9B250EADCFE27D50F09FA76599B0 appid="{4dc3e181-e14b-4a21-b022-59fc669b0914}"'
New-WebBinding -name "Default Web Site" -Protocol https -HostHeader * -Port 443 -SslFlags 1

需要注意的是,这里SSL_PASS包含的证书密码,是读取的一个环境变量。因为,pfx格式的证书中包含非常重要的私钥,我们不可以将密码写在脚本中,必须在docker run的时候传入。另外,netsh http add sslcert的参数中,certhash这个参数的值,这里hardcode了iis-test.pfx这个证书的hash值,如果你要安装的是你自己的证书,需要用你自己证书的hash值替换。对于已经安装于当前机器的自定义证书,我们可以通过下面的powershell命令,列出所有的证书和它们的hash值:

Get-ChildItem cert:\LocalMachine\My

再有,这里的SSL我们是设置到机器当前的ip,除了将SSL通过ipport参数绑定到ip,我们也可以通过hostnameport将SSL绑定到hostname,具体请参考netsh http add sslcert的相关文档。另外,这个示例的部分代码参考了这篇文章

本篇完!不过,本文今后还会持续更新,后面有新的Windows Server Dockerfile的武功心法,我会陆续补充到这篇文章,大家也可以关注相关的Github Repo获取更新。

老司机实战Windows Server Docker:5 Windows Server Dockerfile葵花宝典的更多相关文章

  1. 老司机实战Windows Server Docker:2 docker化现有iis应用的正确姿势

    前言 上一篇老司机实战Windows Server Docker:1 初体验之各种填坑介绍了安装docker服务过程中的一些小坑.这一篇,我们来填一些稍大一些的坑:如何docker化一个现有的iis应 ...

  2. 老司机实战Windows Server Docker:3 单节点Windows Docker服务器简单运维(上)

    经过上两篇实战Windows Server Docker系列文章,大家对安装Windows Docker服务以及如何打包现有IIS应用为docker镜像已经有了基本认识.接下来我们来简单讲讲一些最基本 ...

  3. 老司机实战Windows Server Docker:1 初体验之各种填坑

    前言 Windows Server 2016正式版发布已经有近半年时间了,除了看到携程的同学分享了一些Windows Server Docker的实践经验,网上比较深入的资料,不管是中文或英文的,都还 ...

  4. 老司机实战Windows Server Docker:4 单节点Windows Docker服务器简单运维(下)

    上篇中,我们主要介绍了使用docker-compose对Windows Docker单服务器进行远程管理,编译和部署镜像,并且设置容器的自动启动.但是,还有一些重要的问题没有解决,这些问题不解决,就完 ...

  5. 说一说windows原生docker及windows Server Container , Hyper Container 之间的关系(学习总结)

    前一段时间学习netcore的时候解除到了docker,感觉真是不错的技术.百度了不少教程.因为我用windows就下载安装了一下试试.但是没有安装成功,才发现 需要安装virtualbox虚拟机,与 ...

  6. 在Linux和Windows的Docker容器中运行ASP.NET Core

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 译者序:其实过去这周我都在研究这方面的内容,结果周末有事没有来得及总结为文章,Scott H ...

  7. Docker for Windows(四)实践搭建&删除MySQL服务

    我们已经下载安装好了Docker for Windows:Docker for Windows(一)下载与安装,也简单了解了Docker常用命令:Docker for Windows(三)Docker ...

  8. 初识Docker和Windows Server容器

    概览 伴随着Windows Server 2016 Technical Preview 3 (TP3)版本的发布,微软首次提供了Windows平台下地原生容器.它集成了Docker对Windows S ...

  9. Docker实战系列一:初识Docker for Windows

    windows下安装Docker官网教程Install Docker for Windows Docker配置官网教程Get started with Docker for Windows

随机推荐

  1. oracle用户创建,连接,删除

    绕过管理员身份进行登录: sqlplus /nolog conn /as sysdba 如何创建一个普通用户: 1.create user jsd1412 identified by jsd1412 ...

  2. CI Weekly #14 | 如何搭建合适的持续交付开发流程?

    时隔 10 个月,flow.ci 开始正式收费上线.为感谢对我们的内测支持,所有内测用户可继续免费使用基础版 30 天,截止至 3 月 15 日失效.欢迎随时告诉我们你对收费版 flow.ci 的反馈 ...

  3. Weinre 远程调试移动端页面

    Weinre 是一款远程调试工具,使用JS编写, 可以让我们在电脑上直接调试运行在手机上的远程页面,当你的代码已经发布上线,这时候出现了问题,Weinre就可以帮你调试. 调试场景 调试页面在手机上. ...

  4. 网站Web业务架构从小到大演变

    有一天,我突发奇想创建了一个站点,基于 LNMP 架构,起初只有我自己访问,后来因为我点儿正,访问量越来越大,所以最终导致下面的架构演变. 1.单台机器 单台机器因为只是一个小站,访问量一天也没有多少 ...

  5. 一个想法照进现实-《IT连》创业项目:万事开头难

    前言: 之前是一个想法,现在已经进入创业阶段,所以这个系列的标题,改了. 众筹的事在今天也停止了. 7-9号会在深圳龙岗布吉参加一个风投对接的活动,今晚(6号)会出发. 因为:在深圳会呆几天,而且这个 ...

  6. 面向对象 "一"

    1:面向对象不是所有情况都适用. 2面向对象编程 a:定义类 calss Foo: 注意顶一个类的时候首字母必须是大写 def (方法一)(self,bb) pass b:根据创建对象,创建和Foo实 ...

  7. php 实现购物车功能,以大苹果购物网为例,上图上代码。。。。

    首先是几个简单的登录页面 <body> <form action="chuli.php" method="post"> <div ...

  8. linux网络编程1 最简单的socket编程

    下面是socket编程的服务器端 先看一个图,1 #include<stdio.h> #include<stdlib.h> #include<string.h> # ...

  9. Java设计模式之《代理模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6525527.html 代理模式算是我接触较早的模式,代理就是中介,中间人.法律上也有代理, ...

  10. Android--多线程之Handler 前言

    前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不 允许Activity新启动的线程访问该Activity里的 ...