VMWare EsxiとJenkinsとGradleでインフラのコード化&CI

要件は

  • 作業のログは永遠に残す必要がある
  • いつ、誰が、どういった理由でインフラを変更したかわかるようにする

ひとつ、落とすと

  • インフラをコードで書けるようにする
  • 開発、本番共にVMWareで構成された基盤なのでVMWare仮想マシンが作れるコードを書く
  • インフラコードをバージョン管理し、変更毎に構築できるかテストする
  • コードで構築されたインフラでアプリケーションの全てのテストが通るか確認する(インフラの要件は設定ファイルがあるかどうかではなく、アプリケーションが動作するかどうか)
  • テストは既に動作しているCIがあるので、それを使い回す
  • ログの保持もJenkinsにしてもらおう

やること

  • Jenkinsから仮想マシン作成、OSのインストール
  • Jenkinsのノードに作成された仮想マシンを追加
  • 追加されたノードでOSインストール後の設定やミドルウェアのインストール
  • 環境の整ったノードに対してアプリのデプロイとテスト

インフラ定義Gradleファイル

applyEx {
  plugins 'java_ext',
          'sh',
          'tomcat_ext',
          'vm',
          'mkdir',
          'proxy_auth'
  files   'common/yum_update.gradle',
          'common/set_ntp.gradle',
          'common/install_httpd.gradle',
          'common/install_ssh.gradle',
          'common/set_secretkey.gradle'
}

vCenter {
  url = 'http://xxx.xxx.xxx.xxx/sdk'
  user = 'root'
  passwd 'JM*rtTS**MfJgDt**CXXZ*4gNiyB***jssVS'
}

vm {
  description = 'テスト用アプリケーションサーバ'
  name = 'testap'

  registry {
    center = 'vCenter'
    datastore = 'test'
    host = 'test_cluster'
  }

  cpu {
    core = 4
    socket = 1
  }
  memory {
    sizeGB = 8
  }
  hdd {
    sizeGB = 128
    type = 'thin'
  }

  iso {
    file = 'iso/CentOS-6.4-x86_64-netinstall.iso'
    datastore = 'test'
  }

  network {
    name = 'VM Network'
    ip = '192.168.**.**'
    openPorts = ['http', '5900:tcp', '8080:tcp', '8081:tcp']
    gateway = '192.168.**.**'
  }
  passwd 'JM*rtTS**MfJgDt**CXXZ*4gNiyB***jssVS'
}

dir {
  root {
    var {
      log {
        testApp(perm:755)
      }
    }
  }
}

java {
  version = '7u21'
}

tomcat {
  version = '7.0.40'

  option = ['-Dfile.encoding=UTF-8',
            '-Dcatalina.logbase=/var/log/tomcat',
            '-Dnet.sf.ehcache.skipUpdateCheck=true',
            '-XX:-UseSplitVerifier',
            '-XX:+DoEscapeAnalysis',
            '-XX:+CMSClassUnloadingEnabled',
            '-XX:+UseParNewGC',
            '-XX:+UseConcMarkSweepGC',
            '-XX:PermSize=128m -XX:MaxPermSize=256m',
            '-Xms256m -Xmx512m',
            '-server']
}

システムの設定 << {
  os.insert('/etc/sysctl.conf'.asType(File), 'vm.swappiness = 30')
  os.execute('sysctl -p')
}

//固有設定を追加
Httpdのインストール << {
  '/etc/httpd/conf/httpd.conf'.asType(File).with {
    write text.replaceAll('#ServerName www.example.com:80',"ServerName ${vm.name}:80")
    write text.replaceAll('Options Indexes FollowSymLinks','Options FollowSymLinks')
    println path
    println text
  }

  '/etc/httpd/conf.modules.d/01-mpm.conf'.asType(File).with {
    write '''# event MPM
            |# StartServers: initial number of server processes to start
            |# MinSpareThreads: minimum number of worker threads which are kept spare
            |# MaxSpareThreads: maximum number of worker threads which are kept spare
            |# ThreadsPerChild: constant number of worker threads in each server process
            |# MaxRequestWorkers: maximum number of worker threads
            |# MaxConnectionsPerChild: maximum number of connections a server process serves
            |#                         before terminating
            |<IfModule mpm_event_module>
            |    StartServers             2
            |    MinSpareThreads         25
            |    MaxSpareThreads         75
            |    ThreadsPerChild         25
            |    MaxRequestWorkers      150
            |    MaxConnectionsPerChild   0
            |</IfModule>'''.stripMargin()
    println path
    println text
  }
}

task installModJk << {
  os.installRPM('http://repo/repos/CentOS/6/x86_64/', 'mod_jk-1.2.37-1.x86_64.rpm')
  new File('/etc/httpd/conf.modules.d/00-mod_jk.conf').with {
  new File('/etc/httpd/conf.modules.d/workers.properties').with {
    write '''worker.list=ajp13
            |worker.ajp13.port=8009
            |worker.ajp13.host=localhost
            |worker.ajp13.type=ajp13'''.stripMargin()
  }
  def conf = '/etc/httpd/conf.modules.d/00-proxy.conf'.asType(File)
  conf.write(conf.text.replaceAll('^LoadModule proxy_ajp_module','#LoadModule proxy_ajp_module'))
}

秘密鍵の設定 {
  host = 'serverA'
  user = 'root'
  password = '1to*oCY*et06axq*9TahD***QvRxu**/n8e'
}

defaultTasks (
  'システムの更新',
  'VMWareToolsのインストール',
  'VMWareToolsの更新',
  'mkdir',
  'Javaの設定',
  'Ntpの設定',
  'Httpdのインストール',
  'Tomcatのインストール',
  'modJkのインストール',
  'システムの設定',
  '秘密鍵の設定',
  '再起動'
)

このコードを実行すると仮想マシンが作成されるので、
その後、Jenkins上のスクリプトでノードに追加し、ミドルウェアの設定を実行するジョブを後続として流す。
ミドルの変更前にスナップショットが取られるので、テストがうまくいったら削除する(ここはまだ自動化してない)。
見えてない部分のコードでは泥臭くyum installとかwgetとかやっていて、VMの作成はvijavaを使用している。
ミドルウェアの設定ジョブが終わったら、テストジョブを後続として流して、インフラのCI環境の完成。

次にやりたいのは設定や、ミドルウェアをマトリクスで一斉にパフォーマンステストして・・・みたいなことをやりたい。