diff --git a/.kitchen.local.yml.example b/.kitchen.local.yml.example new file mode 100644 index 0000000..7a3e3f6 --- /dev/null +++ b/.kitchen.local.yml.example @@ -0,0 +1,70 @@ +--- +driver: + name: gce + project: chef-marketplace-dev + zone: us-central1-a + preemptible: true + email: matt@chef.io + tags: + - mesosphere + - test-kitchen + service_account_scopes: + - devstorage.read_write + - userinfo.email + +transport: + username: mray + ssh_key: + - ~/.ssh/google_compute_engine + +platforms: + - name: centos-7.2 + driver: + image_name: centos-7 + +suites: + - name: default + run_list: + - recipe[dcos::default] + attributes: + dcos: + master_list: ['127.0.0.1'] + - name: gm1 + run_list: + - recipe[dcos::default] + attributes: + dcos: + ip-detect: 'gce' + dcos_role: 'master' + master_list: ['10.240.0.2', '10.240.0.3', '10.240.0.4'] + - name: gm2 + run_list: + - recipe[dcos::default] + attributes: + dcos: + ip-detect: 'gce' + dcos_role: 'master' + master_list: ['10.240.0.2', '10.240.0.3', '10.240.0.4'] + - name: gm3 + run_list: + - recipe[dcos::default] + attributes: + dcos: + dcos_role: 'master' + master_list: ['10.240.0.2', '10.240.0.3', '10.240.0.4'] + - name: gs1 + run_list: + - recipe[dcos::default] + attributes: + dcos: + ip-detect: 'gce' + dcos_role: 'slave' + master_list: ['10.240.0.2', '10.240.0.3', '10.240.0.4'] + - name: gs2 + run_list: + - recipe[dcos::default] + attributes: + dcos: + ip-detect: 'gce' + dcos_role: 'slave' + master_list: ['10.240.0.2', '10.240.0.3', '10.240.0.4'] diff --git a/.kitchen.yml b/.kitchen.yml index 8234213..3eec52e 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -14,5 +14,7 @@ platforms: suites: - name: default run_list: - - recipe[mesosphere::default] + - recipe[dcos::default] attributes: + dcos: + master_list: ['127.0.0.1'] diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..d53c800 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,6 @@ +Style/FileName: + Exclude: + - 'recipes/_ip-detect.rb' + +LineLength: + Enabled: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f731bf0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: ruby +sudo: false + +addons: + apt: + sources: + - chef-current-precise + packages: + - chefdk + +# Ensure we make ChefDK's Ruby the default +before_script: + - eval "$(/opt/chefdk/bin/chef shell-init bash)" +script: + - /opt/chefdk/embedded/bin/chef --version + - /opt/chefdk/embedded/bin/rubocop --version + - /opt/chefdk/embedded/bin/rubocop + - /opt/chefdk/embedded/bin/foodcritic --version + - /opt/chefdk/embedded/bin/foodcritic . --exclude spec + - /opt/chefdk/embedded/bin/rspec --version + - /opt/chefdk/embedded/bin/rspec diff --git a/Berksfile b/Berksfile new file mode 100644 index 0000000..34fea21 --- /dev/null +++ b/Berksfile @@ -0,0 +1,3 @@ +source 'https://supermarket.chef.io' + +metadata diff --git a/README.md b/README.md index 93057c1..7ea1325 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,70 @@ +[![Build Status](https://travis-ci.org/chef-partners/dcos-cookbook.svg?branch=master)](https://travis-ci.org/chef-partners/dcos-cookbook) + Description =========== -Manage deployment and configuration of underlying Mesosphere installation. +Manage deployment and configuration of underlying Mesosphere DCOS installation. + +Requirements +------------ -Attributes +Only Red Hat or CentOS 7.x are currently supported. + +Usage ========== +The behavior of this cookbook is managed by attributes documented in the [attributes file](attributes/default.rb). The `node['dcos']['dcos_role']` attribute controls the DCOS role to apply to the node (default is `master`). The `node['dcos']['master_list']` must be set to specify the list of DCOS master node IPv4 addresses to connect at startup (this must be an odd number of masters). + +Roles +---------- + +You can create a Chef Role and apply it to nodes as necessary to specify `master`, `slave` and `slave-public` as appropriate. Any additional configuration should probably be set as override attributes in an Environment to ensure all nodes receive those global settings. + +### Example Role dcos_master.rb ### +````ruby +name "dcos_master" +description "DCOS master role" +run_list "recipe[dcos]" +default_attributes "dcos" => { + "dcos_role" => "master" + "master_list" => [ "10.0.2.10" ] +} +```` + +### Example Role dcos_slave.rb ### +````ruby +name "dcos_slave" +description "DCOS slave role" +run_list "recipe[dcos]" +default_attributes "dcos" => { + "dcos_role" => "slave" + "master_list" => [ "10.0.2.10" ] +} +```` + Recipe ======= default ------- +Installs the prerequisites for the Mesosphere DCOS installation, including packages, groups and Docker with OverlayFS enabled. It then downloads and runs the installation package with the settings configured by the node's attributes. + +Testing +======= + +ChefSpec +-------- +There is basic coverage for the default recipe. + +InSpec +------ +TBD + +Test Kitchen +------------ +The included [.kitchen.yml](.kitchen.yml) runs the default master deployment in a generic fashion. The included [.kitchen.local.yml.example](.kitchen.local.yml.example) shows alternate settings for running multi-master with slaves on GCE (you will have to rename and update accordingly). + License and Author ================== diff --git a/attributes/default.rb b/attributes/default.rb new file mode 100644 index 0000000..b833878 --- /dev/null +++ b/attributes/default.rb @@ -0,0 +1,37 @@ +# +# Author:: Matt Ray +# Cookbook Name:: dcos +# +# Copyright 2016 Chef Software, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +default['dcos']['dcos_version'] = 'stable' +default['dcos']['dcos_role'] = 'master' # 'master', 'slave' or 'slave_public' + +default['dcos']['cluster_name'] = 'DCOS' +default['dcos']['master_discovery'] = 'static' +default['dcos']['exhibitor_storage_backend'] = 'static' +default['dcos']['bootstrap_url'] = 'file:///root/genconf/serve' + +# determine how to generate the genconf/ip-detect script +# 'aws' or 'gce' will use the local ipv4 address from the metadata service +# otherwise use 'eth0', 'eth1', etc. and it will get the ipaddress associated +# with node['network']['interface'][VALUE] +default['dcos']['ip-detect'] = 'eth0' + +# ipv4 only, must be odd number 1-9 +default['dcos']['master_list'] = [] +# upstream DNS for MesosDNS +default['dcos']['resolvers'] = ['8.8.8.8', '8.8.4.4'] diff --git a/files/default/aws b/files/default/aws new file mode 100644 index 0000000..4ed6aab --- /dev/null +++ b/files/default/aws @@ -0,0 +1,6 @@ +#!/bin/sh +# Example ip-detect script using an external authority +# Uses the AWS Metadata Service to get the node's internal +# ipv4 address + +curl -fsSL http://169.254.169.254/latest/meta-data/local-ipv4 diff --git a/files/default/gce b/files/default/gce new file mode 100644 index 0000000..cbcee8e --- /dev/null +++ b/files/default/gce @@ -0,0 +1,6 @@ +#!/bin/sh +# Example ip-detect script using an external authority +# Uses the GCE metadata server to get the node's internal +# ipv4 address + +curl -fsSl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/network-interfaces/0/ip diff --git a/metadata.rb b/metadata.rb index 7b2da03..77ead45 100644 --- a/metadata.rb +++ b/metadata.rb @@ -1,4 +1,4 @@ -name 'mesosphere' +name 'dcos' maintainer 'Chef Software, Inc.' maintainer_email 'partnereng@chef.io' license 'Apache 2.0' @@ -6,9 +6,15 @@ long_description 'Installs/Configures Mesosphere' version '0.1.0' +source_url 'https://github.com/chef-partners/dcos-cookbook' if + respond_to?(:source_url) +issues_url 'https://github.com/chef-partners/dcos-cookbook/issues' if + respond_to?(:issues_url) + supports 'centos' supports 'oracle' supports 'redhat' supports 'scientific' depends 'docker', '~> 2.0' +depends 'selinux', '~> 0.9.0' diff --git a/recipes/_ip-detect.rb b/recipes/_ip-detect.rb new file mode 100644 index 0000000..ad118da --- /dev/null +++ b/recipes/_ip-detect.rb @@ -0,0 +1,45 @@ +# +# Cookbook Name:: dcos +# Recipe:: _ip-detect +# +# Copyright 2016, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# generates a genconf/ip-detect script based on the node['dcos']['ip-detect'] +# https://docs.mesosphere.com/archived-dcos-enterprise-edition/installing-enterprise-edition-1-5/create-a-script-for-ip-address-discovery/ + +if node['dcos']['ip-detect'].eql? 'aws' + cookbook_file '/root/genconf/ip-detect' do + source 'aws' + mode '0755' + end +elsif node['dcos']['ip-detect'].eql? 'gce' + cookbook_file '/root/genconf/ip-detect' do + source 'gce' + mode '0755' + end +else + # find the ipaddress for that interface + interface = node['dcos']['ip-detect'] + addresses = node['network']['interfaces'][interface]['addresses'] + addresses.select do |ip, data| + template '/root/genconf/ip-detect' do + source 'ip-detect.erb' + only_if { data['family'].eql?('inet') } + mode '0755' + variables(ip: ip) + end + end +end diff --git a/recipes/default.rb b/recipes/default.rb new file mode 100644 index 0000000..8d1c962 --- /dev/null +++ b/recipes/default.rb @@ -0,0 +1,81 @@ +# +# Cookbook Name:: dcos +# Recipe:: default +# +# Copyright 2016, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Prereqs +selinux_state 'SELinux Permissive' do + action :permissive +end + +service 'firewalld' do + action [:stop, :disable] +end + +package 'unzip' + +package 'ipset' + +group 'nogroup' + +# Install docker with overlayfs +docker_service 'default' do + storage_driver 'overlay' + action [:create, :start] +end + +# Note this link does not have versioning, so using it will always be the latest +# DCOS from 'testing' and this URL might change in the future. +# https://s3.amazonaws.com/downloads.mesosphere.io/dcos/stable/dcos_generate_config.sh +remote_file '/root/dcos_generate_config.sh' do + source 'https://downloads.mesosphere.io/dcos/testing/continuous/dcos_generate_config.sh' + mode '0755' +end + +directory '/root/genconf' do + mode '0755' +end + +template '/root/genconf/config.yaml' do + source 'config.yaml.erb' +end + +# generate the /root/genconf/ip-detect script +include_recipe 'dcos::_ip-detect' + +execute 'dcos-genconf' do + command '/root/dcos_generate_config.sh --genconf' + user 'root' + cwd '/root' + not_if do + File.exist?('/root/genconf/cluster_packages.json') + end +end + +file '/root/genconf/serve/dcos_install.sh' do + mode '0755' + subscribes :create, 'execute[dcos-genconf]', :immediately + action :nothing +end + +execute 'dcos_install' do + command "/root/genconf/serve/dcos_install.sh #{node['dcos']['dcos_role']}" + user 'root' + cwd '/root' + subscribes :run, 'file[/root/genconf/serve/dcos_install.sh]', :immediately + action :nothing +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..1dd5126 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require 'chefspec' +require 'chefspec/berkshelf' diff --git a/spec/unit/recipes/default_spec.rb b/spec/unit/recipes/default_spec.rb new file mode 100644 index 0000000..84ac316 --- /dev/null +++ b/spec/unit/recipes/default_spec.rb @@ -0,0 +1,68 @@ +# +# Cookbook Name:: dcos +# Spec:: default +# + +require 'spec_helper' + +describe 'dcos::default' do + context 'Default behavior, assume eth0' do + let(:chef_run) do + ChefSpec::SoloRunner.new do |node| + node.set['network']['interfaces']['eth0']['addresses'] = + { + '1.2.3.4' => + { + 'family' => 'inet', + 'netmask' => '255.255.255.0', + 'broadcast' => '192.168.1.255' + } + } + end.converge(described_recipe) + end + + it 'stops service firewalld' do + expect(chef_run).to stop_service('firewalld') + end + + it 'disables service firewalld' do + expect(chef_run).to disable_service('firewalld') + end + + it 'installs unzip' do + expect(chef_run).to install_package('unzip') + end + + it 'installs ipset' do + expect(chef_run).to install_package('ipset') + end + + it 'creates group[nogroup]' do + expect(chef_run).to create_group('nogroup') + end + + it 'creates remote_file[dcos_generate_config.sh]' do + expect(chef_run).to create_remote_file('/root/dcos_generate_config.sh').with(mode: '0755') + end + + it 'creates directory[genconf]' do + expect(chef_run).to create_directory('/root/genconf').with(mode: '0755') + end + + it 'creates template[config.yaml]' do + expect(chef_run).to create_template('/root/genconf/config.yaml') + end + + it 'executes[dcos-genconf]' do + expect(chef_run).to run_execute('dcos-genconf').with(user: 'root') + end + + it 'executes[dcos_install]' do + expect(chef_run).to run_execute('dcos-genconf').with(user: 'root') + end + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + end + end +end diff --git a/templates/default/config.yaml.erb b/templates/default/config.yaml.erb new file mode 100644 index 0000000..f1e7ee1 --- /dev/null +++ b/templates/default/config.yaml.erb @@ -0,0 +1,13 @@ +--- +cluster_name: '<%= node["dcos"]["cluster_name"] %>' +master_discovery: '<%= node["dcos"]["master_discovery"] %>' +exhibitor_storage_backend: '<%= node["dcos"]["exhibitor_storage_backend"] %>' +bootstrap_url: '<%= node["dcos"]["bootstrap_url"] %>' +master_list: +<% node["dcos"]["master_list"].each do |ip| %> + - <%= ip %> +<% end %> +resolvers: +<% node["dcos"]["resolvers"].each do |ip| %> + - <%= ip %> +<% end %> diff --git a/templates/default/ip-detect.erb b/templates/default/ip-detect.erb new file mode 100644 index 0000000..c27d8b9 --- /dev/null +++ b/templates/default/ip-detect.erb @@ -0,0 +1,5 @@ +#!/bin/sh + +# generated by the _ip-detect recipe based on the interface specified + +echo '<%= @ip %>' diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb new file mode 100644 index 0000000..d381294 --- /dev/null +++ b/test/integration/default/serverspec/default_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe 'dcos::default' do + # Serverspec examples can be found at + # http://serverspec.org/resource_types.html + it 'does something' do + skip 'Replace this with meaningful tests' + end +end diff --git a/test/integration/helpers/serverspec/spec_helper.rb b/test/integration/helpers/serverspec/spec_helper.rb new file mode 100644 index 0000000..c1fddf0 --- /dev/null +++ b/test/integration/helpers/serverspec/spec_helper.rb @@ -0,0 +1,8 @@ +require 'serverspec' + +if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM).nil? + set :backend, :exec +else + set :backend, :cmd + set :os, family: 'windows' +end