« 上一篇下一篇 »

如何通过云容器基础设施测试来验证MySQL Server Docker镜像

  说到部署,Docker将便携性和易用性拉高到一个新水准。MySQL相关的Dockerfile和脚本已经发布很长时间,在开发社区的使用率也稳步增长。这一点也在意料之中。

在影响到MySQL性能的每个环节上,用户的典型担忧在于:容器化以后,在这些环节上是否存在显著的性能开销。为此,我们进行了充分的性能测试,下面我会对测试结果的某些细节进行探讨。

  我们的关注点主要在MySQL实例的IO和网络性能,尤其是对比采用了不同存储选项的实例,以及docker bridge网络模式带来了多少性能开销。 测试的运行环境是:Oracle Server x5-2,处理器为2x Xeon E5-2660 v3(40硬件线程),256G内存,操作系统Ubuntu 16.04,Docker版本v1.11.2。
  在数据库MySQ中L,我们有很多基础设施,我们越来越多地使用容器来代替真实(虚拟)机器。此外,越来越多的核心基础设施运行在Oracle的云基础设施(OCI)上。这要求我们实现多个级别的自动化,并且可以利用基础设施测试来验证我们的服务器(或虚拟机、容器)的状态。基础设施测试还用于验证我们发布的一些工件的状态。

  传统的基础设施管理是一项手动任务,由系统管理员管理静态服务器。现代云平台的自动化能力改变了这种工作方式:基础设施通常被描述为“代码”,基础设施管理系统会对基础设施自动做出变更。因此,基础设施的变得更加动态,周转时间也要短得多。

  基础设施测试框架通常被用于验证机器镜像的状态(Amazon Machine Images、Google Compute Images或Oracle OCI Images)。随着容器基础设施的出现,容器基础设施的测试变得与机器镜像的测试一样重要。

  在这篇博文中,我们将重点介绍如何使用自动化基础设施测试来验证MySQL Server Docker镜像。我们将比较三个可用于进行容器测试的框架,并给出示例代码。
自动化基础设施测试
基础设施测试用于测试基础设施的状态:Apache服务器是否在监听80端口?是否正确配置了DNS服务器,这些设置是否正确反映在resolv.conf文件中?要安装的二进制文件是否都已经存在于机器镜像中?

  这类测试可以作为bash脚本的一部分,因此通常被用于配置任务,或者在(手动)创建实例后进行手动验证。自动化基础设施测试更进一步,它假设需要对很多基础设施和脚本的正确性做出验证,以及动态的现代云环境有很多东西需要通过手动的方式处理。

基础设施测试工具通常与Ansible、Puppet或Chef等配置工具结合使用。配置工具在机器上安装软件,测试框架则确保它们能够正常运行。然后,任何东西都可以通过代码来表示,并使用工具进行自动化。

  我们的重点是测试Docker镜像,对我们来说,这些工作有点偏向底层。由于我们所有的Docker镜像主要是由经过测试并发布的yum软件包层组成,这些层位于非常可靠的OS层之上,我们主要想要验证软件包的版本是否正确,以及这些二进制文件的功能是否正常。在镜像构建期间,可能会发生网络故障,出现软件包安装不完整的情况,我们想要通过自动化测试来捕捉它们。

在评估测试工具时,需要考虑到以下两个方面的问题:

配置语言,即想要测试的内容(可用包、必要的文件等)
测试执行,即如何运行测试(local/ssh/container)
对于以下的工具,我们将关注这两个方面的问题。这个领域最常见的工具包括:

InSpec/Serverspec
Goss
Container Structure Test
接下来,我们将逐个简要介绍它们。

InSpec
InSpec基于RSpec(Ruby)测试框架,并借鉴了Serverspec(也是基于RSpec构建,并被广泛采用)的经验。它是Chef生态系统的一部分,用于配置和测试基础设施。它的配置保存在一个ruby文件中。

可以通过resources指定多种配置语言

通过targets(local/ssh/docker)来测试执行

Goss
Goss是Serverspec的一个快速而简单的替代品,是使用Go语言开发的一个服务器测试和验证框架。它的配置保存在一个yaml文件中,这个文件可以很方便地从当前系统状态生成。

支持多种配置语言

支持在本地和Docker容器中执行测试(通过dgoss脚本)

Container Structure Test
Container Structure Test是一个用于验证容器镜像结构的框架。与Goss一样,它也是用Go语言编写,并使用了yaml配置文件。该项目于今年早些时候发布,它的应用范围相对较窄(只支持容器),但它提供了足够的功能来测试镜像。

支持的配置语言较少
测试执行仅限于本地容器
示例:MySQL Server Images
接下来,我们将演示如何安装所需工具,解释各个配置文件,并在本地运行测试。我们针对最新的MySQL Server容器(latest或8.0标签)运行测试。为了方便起见,我们跳过构建步骤,从公共注册表下载容器并在本地运行测试。在我们的构建管道中,我们首先构建容器,运行测试,在运行成功之后才会推送到公共注册表。可以通过输入以下命令来获取最新版本的mysql-server镜像:

docker pull mysql/mysql-server
总的来说,我们想测试两个东西:

容器是否存在主机上,并包含正确的元数据
容器是否包含所有的包和二进制文件
先决条件

除了可用的Docker环境之外,运行该示例还需要在本地安装InSpec、Goss和Container Structure Test。

InSpec的说明可以在这里找到:https:/downloads.chef.io/inspec。在Linux平台上,可以通过运行以下命令安装Goss和Container Structure Test二进制文件:

curl -L https:/github.com/aelsabbahy/goss/releases/download/v0.3.6/goss-linux-amd64 -o goss && chmod +x goss
curl -L https:/raw.githubusercontent.com/aelsabbahy/goss/master/extras/dgoss/dgoss -o dgoss && chmod +x dgoss
curl -L https:/storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 -o container-structure-test && chmod +x container-structure-test
安装好所有二进制文件并将它们添加到系统路径中,然后就可以通过shell脚本运行测试。

测试配置

为了比较配置和测试执行过程的不同之处,我们提供了用于测试这三个框架的MySQL Server Docker镜像的示例文件:https://github.com/neumayer/mysql-server-image-tests。

可以通过以下命令来克隆它:

git clone https://github.com/neumayer/mysql-server-image-tests.git
存储库中包含的配置文件:

mysql-server-inspec.rb
goss.yaml
mysql-server-container-structure-test.yml
让我们来依次查看这些文件,先从InSpec配置文件开始:

control 'container' do                                                                                            
    impact 0.5                                                                                                      
    describe docker_container('mysql-server') do                                                                    
      it { should exist }                                                                                           
      it { should be_running }                                                                                      
      its('repo') { should eq 'mysql/mysql-server' }                                                                
      its('ports') { should eq '3306/tcp, 33060/tcp' }                                                              
      its('command') { should match '/entrypoint.sh mysqld' }                                                       
    end                                                                                                             
  end                                                                                                               
  control 'server-package' do                                                                                       
    impact 0.5                                                                                                      
    describe package('mysql-community-server-minimal') do                                                           
      it { should be_installed }                                                                                    
      its ('version') { should match '8.0.12.*' }                                                                   
    end                                                                                                             
  end                                                                                                               
  control 'shell-package' do                                                                                        
    impact 0.5                                                                                                      
    describe package('mysql-shell') do                                                                              
      it { should be_installed }                                                                                    
      its ('version') { should match '8.0.12.*' }                                                                   
    end                                                                                                             
  end
InSpec通过profile和control来组织测试用例,其中control是较小的单元,是与给定主题相关的一组测试。第一个control叫“container”,针对宿主机器运行,与运行在localhost上的Docker守护进程通信,验证容器是否正在运行。另外两个control检查容器内的包。这种区别很重要,因为后两个control可以针对localhost、ssh主机或Docker容器运行。在我们的例子中,我们让它们针对容器运行,这样可以带来非常好的可重用性和灵活性。虽然我们在示例中只使用了Docker和包资源,但实际上control可以使用任何现有的InSpec资源。

下面是运行流程:

启动容器
针对localhost运行 “container” control
针对容器运行剩余的control
脚本看起来是这样的:

docker run -d --name mysql-server mysql/mysql-server
inspec exec mysql-server-inspec.rb --controls container
inspec exec mysql-server-inspec.rb -t docker://mysql-server --controls server-package
如果运行成功,InSpec将输出以下内容:

Profile: tests from mysql-server-inspec.rb (tests from mysql-server-inspec.rb)
Version: (not specified)
Target:  local://

  ✔  container: Docker Container mysql-server
     ✔  Docker Container mysql-server should exist
     ✔  Docker Container mysql-server should be running
     ✔  Docker Container mysql-server repo should eq "mysql/mysql-server"
     ✔  Docker Container mysql-server ports should eq "3306/tcp, 33060/tcp"
     ✔  Docker Container mysql-server command should match "/entrypoint.sh mysqld"


Profile Summary: 1 successful control, 0 control failures, 0 controls skipped
Test Summary: 5 successful, 0 failures, 0 skipped

Profile: tests from mysql-server-inspec.rb (tests from mysql-server-inspec.rb)
Version: (not specified)
Target:  docker://d06da2588b80a4ee9b839b55c2f719ab9e860904eeb831b71488704f50f8b994

  ✔  server-package: System Package mysql-community-server-minimal
     ✔  System Package mysql-community-server-minimal should be installed
     ✔  System Package mysql-community-server-minimal version should match "8.0.12.*"


Profile Summary: 1 successful control, 0 control failures, 0 controls skipped
Test Summary: 2 successful, 0 failures, 0 skipped
Goss的配置文件如下所示:

file:                                                                                                             
    /usr/sbin/mysqld:                                                                                               
      exists: true                                                                                                  
      contains: []                                                                                                  
  package:                                                                                                          
    mysql-community-server-minimal:                                                                                 
      installed: true                                                                                               
    mysql-shell:                                                                                                    
      installed: true                                                                                               
  port:                                                                                                             
    tcp6:3306:                                                                                                      
      listening: true                                                                                               
      ip: []                                                                                                        
    tcp6:33060:                                                                                                     
      listening: true                                                                                               
      ip: []                                                                                                        
  user:                                                                                                             
    mysql:                                                                                                          
      exists: true                                                                                                  
  process:                                                                                                          
    mysqld:                                                                                                         
      running: true
除了mysqld文件,我们还要检查所需的软件包是否已安装、公开端口是否正确以及所需的进程是否在运行。Goss将为我们启动容器:

GOSS_SLEEP=10 dgoss run -p 3306:3306 mysql/mysql-server
因为设置了GOSS_SLEEP,所以我们的服务器有足够时间完成初始化,其余参数被传给docker run。输出如下:

INFO: Starting docker container
INFO: Container ID: 75bc8869
INFO: Sleeping for 10
INFO: Running Tests
File: /usr/sbin/mysqld: exists: matches expectation: [true]
User: mysql: exists: matches expectation: [true]
Process: mysqld: running: matches expectation: [true]
Port: tcp6:33060: listening: matches expectation: [true]
Port: tcp6:33060: ip: matches expectation: [[]]
Port: tcp6:3306: listening: matches expectation: [true]
Port: tcp6:3306: ip: matches expectation: [[]]
Package: mysql-shell: installed: matches expectation: [true]
Package: mysql-community-server-minimal: installed: matches expectation: [true]

Total Duration: 0.038s
Count: 9, Failed: 0, Skipped: 0
INFO: Deleting container
Container Structure Test的yaml配置片段如下:

schemaVersion: "2.0.0"                                                                                            
  metadataTest:                                                                                                     
    exposedPorts: [ "3306", "33060" ]                                                                               
    entrypoint: [ "/entrypoint.sh" ]                                                                                
    cmd: [ "mysqld" ]                                                                                               
    volumes: [ "/var/lib/mysql" ]                                                                                   
  commandTests:                                                                                                     
    - name: "mysqlsh"                                                                                               
      command: "mysqld"                                                                                             
      args:                                                                                                         
        - "--version"                                                                                               
      expectedOutput:                                                                                               
        - "8.0.12"                                                                                                  
    - name: "mysqlsh"                                                                                               
      command: "mysqlsh"                                                                                            
      args:                                                                                                         
        - "--version"                                                                                               
      expectedOutput:                                                                                               
        - "8.0.12"                                                                                                  
  fileExistenceTests:                                                                                               
    - name: "mysqld"                                                                                                
      path: "/usr/sbin/mysqld"
此外,我们还要检查公开的端口是否正确,然后直接运行二进制文件(而不是像其他工具那样使用内部包装器)以验证它们是否已就位。

container-structure-test --image mysql/mysql-server test --config mysql-server-container-structure-test.yml
与Goss类似,调用起来很简单,只需要提供镜像名称和配置文件。

==================================================================
====== Test file: mysql-server-container-structure-test.yml ======
==================================================================

INFO: stdout: /usr/sbin/mysqld  Ver 8.0.12 for Linux on x86_64 (MySQL Community Server - GPL)
 
=== RUN: Command Test: mysqlsh
--- PASS
stdout: /usr/sbin/mysqld  Ver 8.0.12 for Linux on x86_64 (MySQL Community Server - GPL)
INFO: stdout: mysqlsh   Ver 8.0.12 for Linux on x86_64 - for MySQL 8.0.12 (MySQL Community Server (GPL))
 
=== RUN: Command Test: mysqlsh
--- PASS
stdout: mysqlsh   Ver 8.0.12 for Linux on x86_64 - for MySQL 8.0.12 (MySQL Community Server (GPL))
INFO: File Existence Test: mysqld                 
=== RUN: File Existence Test: mysqld
--- PASS
=== RUN: Metadata Test
--- PASS

===================================================================
============================= RESULTS =============================
===================================================================
Passes:      4
Failures:    0
Total tests: 4

PASS
Container Structure Test运行速度快,易于调用,只不过只能用于容器。在大多数情况下,它可以确保容器的行为是正确的。

可以在存储库的根目录运行以下脚本来执行上述的测试:

./inspec.sh
./goss.sh
./container-structure-test.sh
MySQL的容器测试
我们已经开始使用InSpec测试所有的Docker镜像。从下一个MySQL版本(8.0.13)开始,基本的InSpec测试将成为MySQL Server、MySQL Cluster和MySQL Router Docker镜像自动发布流程的一部分。我们主要基于以下几点做出这样的决定:

它拥有最大的作用域(ssh/local/docker),可进一步在内部使用;
广泛的资源;
依赖机制;
由于它与Chef生态系统的关系以及与Serverspec的相似性而被广泛采用。
存在的不足:

因为是ruby调用,运行速度感觉比Go语言的替代品慢(但我们认为这不是关键问题);
InSpec更通用,但需要做出权衡,用更多的脚本来运行测试。
我们将InSpec作为自动发布管道的一部分,如果测试失败,将不会生成任何工件。我们的QA流程包含很多其他步骤,例如之前对MySQL Docker镜像中的rpm包进行的单独测试。

 

结论
一个关键因素是I/O负载,I/O负载过高会抹平MySQL在Docker和宿主机上的差异。当不受I/O负载影响时,Docker显示出小幅度的瓶颈,尤其是运行在bridge网络模式下时。

综合来说,docker不同的存储选项不会对MySQL性能产生较大影响,因此可以随意选择存储。非常重要的一点是,将MySQL运行在容器中时,要通过配置,对其进行性能调优。

容器化MySQL已经在测试和开发环境中大量使用,我们的测试结果也显示在生产环境中选择容器化MySQL并不需要性能方面的考虑
---------------------