Merge pull request 'feat: manage proxmox nodes' (#159) from neoloc/proxmox into develop

Reviewed-on: unkinben/puppet-prod#159
This commit is contained in:
Ben Vincent 2024-04-21 15:07:43 +09:30
commit ccf43f3bcb
33 changed files with 564 additions and 7 deletions

View File

@ -7,7 +7,7 @@ mod 'puppetlabs-inifile', '6.0.0'
mod 'puppetlabs-concat', '9.0.0'
mod 'puppetlabs-vcsrepo', '6.1.0'
mod 'puppetlabs-yumrepo_core', '2.0.0'
mod 'puppetlabs-apt', '9.1.0'
mod 'puppetlabs-apt', '9.4.0'
mod 'puppetlabs-lvm', '2.1.0'
mod 'puppetlabs-puppetdb', '7.13.0'
mod 'puppetlabs-postgresql', '9.1.0'
@ -17,6 +17,7 @@ mod 'puppetlabs-mysql', '15.0.0'
mod 'puppetlabs-xinetd', '3.4.1'
mod 'puppetlabs-haproxy', '8.0.0'
mod 'puppetlabs-java', '10.1.2'
mod 'puppetlabs-reboot', '5.0.0'
# puppet
mod 'puppet-python', '7.0.0'

View File

@ -1,2 +1,3 @@
---
profiles::accounts::sysadmin::password: ENC[PKCS7,MIIBqQYJKoZIhvcNAQcDoIIBmjCCAZYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAoS7GyofFaXBNTWU+GtSiz4eCX/9j/sh3fDDRgOgNv1qpcQ87ZlTTenbHo9lxeURxKQ2HVVt7IsrBo/SC/WgipAKnliRkkIvo7nfAs+i+kEE8wakjAs0DcB4mhqtIZRuBkLG2Nay//DcG6cltVkbKEEKmKLMkDFZgTWreOZal8nDljpVe1S8QwtwP4/6hKTef5xsOnrisxuffWTXvwYJhj/VXrjdoH7EhtHGLybzEalglkVHEGft/WrrD/0bwJpmR0RegWI4HTsSvGiHgvf5DZJx8fXPZNPnicGtlfA9ccQPuVo17bY4Qf/WIc1A8Ssv4kHSbNIYJKRymI3UFb0Z4wzBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBBxDLb6pCGbittkcX6asd/gEBmMcUNupDjSECq5H09YA70eVwWWe0fBqxTxrr2cXCXtRKFvOk8SJmL0xHAWodaLN9+krTWHJcWbAK8JXEPC7rn]
profiles::accounts::root::password: ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAM79PRxeAZHrDcSm4eSFqU94/LjuSbdUmJWivX/Pa8GumoW2e/PT9nGHW3p98zHthMgCglk52PECQ+TBKjxr+9dTyNK5ePG6ZJEqSHNRqsPGm+kfQj/hlTmq8vOBaFM5GapD1iTHs5JFbGngI56swKBEVXW9+Z37BjQb2xJuyLsu5Bo/tA0BaOKuCtjq1a6E38bOX+nJ+YF1uZgV9ofAEh1YvkcTmnEWYXFRPWd7AaNcWn03V2pfhGqxc+xydak620I47P+FE+qIY72+aQ6tmLU3X9vyA1HLF2Tv572l4a2i+YIk6nAgQdi+hQKznqNL9M9YV+s1AcmcKLT7cfLrjsjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCMWrdCWBQgtW3NOEpERwP+gBA3KDiqe4pQq6DwRfsEXQNZ]

View File

@ -58,6 +58,7 @@ profiles::packages::install:
- ccze
- curl
- dstat
- expect
- gzip
- git
- htop

View File

@ -0,0 +1,8 @@
---
profiles::proxmox::params::pve_members_role: 'roles::infra::proxmox::node'
profiles::proxmox::params::pve_kernel_version: '1.0.1'
profiles::proxmox::params::pve_kernel_release: '6.5.13-5-pve'
profiles::proxmox::params::pve_ceph_repos: true
profiles::proxmox::params::pve_ceph_release: 'reef'
profiles::proxmox::params::pve_ceph_install: true
profiles::proxmox::params::pve_ceph_network: '10.18.15.1/24'

View File

@ -0,0 +1,5 @@
---
profiles::proxmox::params::pve_clusterinit_master: true
profiles::proxmox::params::pve_ceph_mon: true
profiles::proxmox::params::pve_ceph_mgr: true
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,4 @@
---
profiles::proxmox::params::pve_ceph_mon: true
profiles::proxmox::params::pve_ceph_mgr: true
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,4 @@
---
profiles::proxmox::params::pve_ceph_mon: true
profiles::proxmox::params::pve_ceph_mgr: true
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,2 @@
---
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,2 @@
---
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,2 @@
---
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,2 @@
---
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,2 @@
---
profiles::proxmox::params::pve_ceph_osd: true

View File

@ -0,0 +1,7 @@
---
sudo::configs:
ceph-smartctl:
priority: 20
content: |
ceph ALL=NOPASSWD: /usr/sbin/smartctl -x --json=o /dev/*
ceph ALL=NOPASSWD: /usr/sbin/nvme * smart-log-add --json /dev/*

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
require 'facter'
Facter.add('is_pveceph_mgr') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
system('pgrep -x ceph-mgr > /dev/null 2>&1')
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
require 'facter'
Facter.add('is_pveceph_mon') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
system('pgrep -x ceph-mon > /dev/null 2>&1')
end
end

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'facter'
Facter.add('ceph_global_config') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
config_file = '/etc/pve/ceph.conf'
config_hash = {}
in_global_section = false
if File.exist?(config_file)
File.readlines(config_file).each do |line|
line.strip!
# Detect the [global] section and set flag
if line == '[global]'
in_global_section = true
next
end
# Exit the loop once we're out of the global section
break if line.start_with?('[') && in_global_section
# Parse key-value pairs if we are in the global section
if in_global_section && line.include?('=')
key, value = line.split('=', 2).map(&:strip)
config_hash[key] = value
end
end
end
config_hash
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
require 'facter'
Facter.add('pve_ceph_initialised') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
File.exist?('/etc/pve/ceph.conf')
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'facter'
Facter.add('pve_cluster') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
conf_file = '/etc/pve/corosync.conf'
totem_details = {}
in_totem_section = false
if File.exist?(conf_file)
File.foreach(conf_file) do |line|
if line =~ /^\s*totem\s*\{/
in_totem_section = true
elsif line =~ /^\s*\}/ && in_totem_section
break
elsif in_totem_section && line =~ /^\s*(\w+):\s*(.+)$/
key = Regexp.last_match(1).strip
value = Regexp.last_match(2).strip
totem_details[key] = value
end
end
end
totem_details.empty? ? nil : totem_details
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'facter'
Facter.add('pve_cluster_member') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
if Facter::Util::Resolution.which('pvesh')
cluster_status = `pvesh get /cluster/status --output-format json`
if cluster_status.empty?
false
else
require 'json'
status = JSON.parse(cluster_status)
!status.empty?
end
else
false
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'facter'
Facter.add('pve_nodelist') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
conf_file = '/etc/pve/corosync.conf'
node_list = {}
current_node = nil
# rubocop:disable Metrics/BlockNesting
if File.exist?(conf_file)
File.foreach(conf_file) do |line|
if line =~ /^\s*node\s*\{/
current_node = {}
elsif line =~ /^\s*\}/
if current_node
node_name = current_node['name']
node_list[node_name] = current_node if node_name
current_node = nil
end
elsif current_node && line =~ /^\s*(\w+):\s*(.+)$/
key = Regexp.last_match(1).strip
value = Regexp.last_match(2).strip
current_node[key] = value
end
end
end
# rubocop:enable Metrics/BlockNesting
node_list.empty? ? nil : node_list
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'facter'
Facter.add('pve_nodes_active') do
confine enc_role: 'roles::infra::proxmox::node'
setcode do
if Facter::Util::Resolution.which('pvesh')
proxmox_nodes = `pvesh get /nodes --output-format json`
unless proxmox_nodes.empty?
require 'json'
nodes = JSON.parse(proxmox_nodes)
nodes.count
end
end
end
end

View File

@ -0,0 +1,50 @@
# profiles::proxmox::ceph
class profiles::proxmox::ceph {
# include params class
include profiles::proxmox::params
# localise some vars
$network = $profiles::proxmox::params::pve_ceph_network
$size = $profiles::proxmox::params::pve_ceph_size
$min_size = $profiles::proxmox::params::pve_ceph_minsize
# install ceph if it is enabled
if $profiles::proxmox::params::pve_ceph_install {
# initialise the cluster, but only on the clusterinit node and only if its not already initialised
if $profiles::proxmox::params::pve_clusterinit_master and ! $facts['pve_ceph_initialised']{
exec { 'pveceph_init':
command => "/usr/bin/pveceph init --network ${network} --size ${size} --min_size ${min_size}",
user => 'root',
}
}
if $facts['pve_ceph_initialised'] {
# create monitors
if $profiles::proxmox::params::pve_ceph_mon {
# only when its not already a monitor
if ! $facts['is_pveceph_mon'] {
exec { 'pveceph_mon':
command => '/usr/bin/pveceph mon create',
user => 'root',
}
}
}
# create managers
if $profiles::proxmox::params::pve_ceph_mgr {
# only when its not already a manager
if ! $facts['is_pveceph_mgr'] {
exec { 'pveceph_mgr':
command => '/usr/bin/pveceph mgr create',
user => 'root',
}
}
}
}
}
}

View File

@ -0,0 +1,41 @@
# profiles::proxmox::clusterinit
class profiles::proxmox::clusterinit {
# include params class
include profiles::proxmox::params
# localise some vars
$clusterinit_master = $profiles::proxmox::params::pve_clusterinit_master
$clustername = $profiles::proxmox::params::pve_cluster
$membersrole = $profiles::proxmox::params::pve_members_role
# if this is the cluster master
if $clusterinit_master {
# and its not a member of a cluster yet
if ! $facts['pve_cluster_member'] {
# initialise a cluster
exec {'pve_init_cluster':
command => "/usr/bin/pvecm create ${clustername}",
unless => 'pvecm status',
timeout => 60,
}
}
}
$servers_array = sort(query_nodes(
"enc_role='${membersrole}' and country='${facts['country']}' and region='${facts['region']}'",
'networking.fqdn'
))
if ! $profiles::proxmox::params::pve_clusterinit_master {
if !empty($servers_array) {
notify { "Cluster ${profiles::proxmox::params::pve_cluster} detected, proceeding to join...":
}
} else {
notify { "No cluster flag found for ${profiles::proxmox::params::pve_cluster}, not attempting to join":
}
}
}
}

View File

@ -0,0 +1,74 @@
# profiles::proxmox::clusterjoin
class profiles::proxmox::clusterjoin {
# include params class
include profiles::proxmox::params
# localise some vars
$clusterinit_master = $profiles::proxmox::params::pve_clusterinit_master
$clustername = $profiles::proxmox::params::pve_cluster
$membersrole = $profiles::proxmox::params::pve_members_role
$root_password = $profiles::proxmox::params::root_password
# query puppetdb for list of cluster members
$members_array = sort(query_nodes(
"enc_role='${membersrole}' and \
country='${facts['country']}' and \
region='${facts['region']}' and \
pve_cluster.cluster_name='${clustername}'",
'networking.fqdn'
))
# check if the pve kernerl is running
if $facts['kernelrelease'] == $profiles::proxmox::params::pve_kernel_release {
# if this is the cluster master
if $clusterinit_master {
# there are no cluster members in puppetdb
if empty($members_array) {
# and this host isnt already in a cluster by itself
if ! $facts['pve_cluster'] {
# initialise a cluster
exec {'pve_init_cluster':
command => "/usr/bin/pvecm create ${clustername}",
unless => 'pvecm status',
timeout => 60,
}
}
}
}
# for non-masters
if ! $clusterinit_master {
# if there are already members of the cluster
if !empty($members_array) {
# and this host isnt already in a cluster
if ! $facts['pve_cluster'] {
# create an expect script to join the cluster
file { '/usr/local/bin/join_pvecluster.expect':
ensure => file,
owner => 'root',
mode => '0755',
content => template('profiles/proxmox/join_pvecluster.erb'),
}
exec { 'pve_join_cluster':
command => "/usr/local/bin/join_pvecluster.expect '${root_password.unwrap}' '${members_array[0]}'",
require => [File['/usr/local/bin/join_pvecluster.expect'], Package['expect']],
unless => "/usr/bin/pvesh nodes | grep -q '${facts['networking']['hostname']}'",
user => 'root',
}
}
} else {
notify { "No initialised cluster found for ${clustername}, not attempting to join":
}
}
}
}
}

View File

@ -0,0 +1,19 @@
# profiles::proxmox::config
class profiles::proxmox::config {
# include params class
include profiles::proxmox::params
# localise some vars
$clusterinit_master = $profiles::proxmox::params::pve_clusterinit_master
# create pve_facts file
file {'/opt/puppetlabs/facter/facts.d/pve_facts.yaml':
ensure => 'file',
owner => 'root',
group => 'root',
mode => '0600',
content => template('profiles/proxmox/pve_facts.yaml.erb')
}
}

View File

@ -0,0 +1,16 @@
# proxmox::
class profiles::proxmox::init {
#include profiles::proxmox::params
include profiles::proxmox::repos
include profiles::proxmox::install
include profiles::proxmox::clusterjoin
include profiles::proxmox::ceph
include profiles::proxmox::config
Class['profiles::proxmox::repos']
-> Class['profiles::proxmox::install']
-> Class['profiles::proxmox::clusterjoin']
-> Class['profiles::proxmox::ceph']
-> Class['profiles::proxmox::config']
}

View File

@ -0,0 +1,58 @@
# profiles::proxmox::install
class profiles::proxmox::install {
# include params class
include profiles::proxmox::params
# install the pve kernel
package { 'proxmox-default-kernel':
ensure => $profiles::proxmox::params::pve_kernel_version,
notify => Reboot['after_run'],
require => Apt::Source['proxmox'],
}
# reboot into the new kernel
reboot { 'after_run':
apply => finished,
}
if $facts['kernelrelease'] == $profiles::proxmox::params::pve_kernel_release {
# install pve
ensure_packages($profiles::proxmox::params::pve_packages_install, { ensure => 'present', require => Apt::Source['proxmox']})
# remove the old linux kernel metapackage
ensure_packages($profiles::proxmox::params::pve_packages_remove, { ensure => 'absent' })
# install ceph package if requested
if $profiles::proxmox::params::pve_ceph_install {
ensure_packages($profiles::proxmox::params::pve_packages_ceph, { ensure => 'present', require => Apt::Source['ceph'] })
}
# cleanup the old kernel packages
exec { 'remove-linux-kernel-packages':
command => '/usr/bin/apt-get purge -y $(/usr/bin/dpkg --list | /bin/grep "linux-image-6.1" | /usr/bin/awk \'{ print $2 }\')',
onlyif => '/usr/bin/dpkg --list | /bin/grep -q "linux-image-6.1"',
path => ['/usr/bin', '/bin', '/sbin'],
refreshonly => true,
}
# update grup
exec { 'update-grub':
command => '/usr/sbin/update-grub',
path => ['/usr/bin', '/bin', '/sbin'],
refreshonly => true,
}
# update grub after removing kernel packages only
Exec['remove-linux-kernel-packages'] ~> Exec['update-grub']
# prepare for SDN
file_line { 'source-network-interfaces-d':
path => '/etc/network/interfaces',
line => 'source /etc/network/interfaces.d/*',
match => '^source /etc/network/interfaces.d/\*$',
append_on_no_match => true,
}
}
}

View File

@ -0,0 +1,42 @@
# profiles::proxmox::params
class profiles::proxmox::params (
Sensitive[String] $root_password = Sensitive(lookup('profiles::accounts::root::password')),
String $pve_members_role = 'roles::infra::proxmox::node',
String $pve_kernel_version = '1.0.1',
String $pve_kernel_release = '6.5.13-5-pve',
String $pve_cluster = "${::facts['country']}-${::facts['region']}",
Boolean $pve_clusterinit_master = false,
Boolean $pve_ceph_repos = false,
Boolean $pve_ceph_install = false,
Boolean $pve_ceph_mon = false,
Boolean $pve_ceph_mgr = false,
Boolean $pve_ceph_osd = false,
String $pve_ceph_release = 'quincy',
Integer $pve_ceph_size = 3,
Integer $pve_ceph_minsize = 2,
Variant[
Undef,
Stdlib::IP::Address::V4::CIDR
] $pve_ceph_network = undef,
Array $pve_packages_install = [
'proxmox-ve',
'postfix',
'open-iscsi',
'frr-pythontools'
],
Array $pve_packages_remove = [
'os-prober',
'linux-image-amd64'
],
Array $pve_packages_ceph = [
'ceph',
'ceph-common',
'ceph-fuse',
'ceph-mds',
'ceph-volume',
'gdisk',
'nvme-cli'
]
){
}

View File

@ -0,0 +1,37 @@
# profiles::proxmox::repos
class profiles::proxmox::repos {
# include params class
include profiles::proxmox::params
$codename = $facts['os']['distro']['codename']
exec { 'download-proxmox-gpg-key':
command => "/usr/bin/wget https://enterprise.proxmox.com/debian/proxmox-release-${codename}.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-${codename}.gpg",
creates => "/etc/apt/trusted.gpg.d/proxmox-release-${codename}.gpg",
path => ['/usr/bin', '/bin'],
require => File['/etc/apt/trusted.gpg.d/'],
}
file { '/etc/apt/trusted.gpg.d/':
ensure => 'directory',
}
apt::source { 'proxmox':
location => 'http://download.proxmox.com/debian/pve',
repos => 'pve-no-subscription',
include => {
src => false,
},
}
if $profiles::proxmox::params::pve_ceph_repos {
apt::source { 'ceph':
location => "http://download.proxmox.com/debian/ceph-${profiles::proxmox::params::pve_ceph_release}",
repos => 'no-subscription',
include => {
src => false,
},
}
}
}

View File

@ -1,15 +1,10 @@
# /etc/hosts file managed by Puppet
# The following lines are desirable for IPv4 capable hosts
127.0.0.1 <%= @fqdn %> <%= @hostname %>
<%= @facts['networking']['ip'] %> <%= @fqdn %> <%= @hostname %>
127.0.0.1 localhost.localdomain localhost
127.0.0.1 localhost4.localdomain4 localhost4
# The following lines are desirable for IPv6 capable hosts
::1 <%= @fqdn %> <%= @hostname %>
::1 localhost.localdomain localhost
::1 localhost6.localdomain6 localhost6
<% @additional_hosts.each do |host| -%>
<%= host['ip'] %> <%= host['hostname'] %> <%= host['aliases'].join(' ') if host['aliases'] %>
<% end -%>

View File

@ -0,0 +1,11 @@
#!/usr/bin/expect -f
set timeout -1
set password [lindex $argv 0]
set ip [lindex $argv 1]
spawn pvecm add $ip
expect "Please enter superuser (root) password for"
send "$password\r"
expect "The authenticity of host"
send "yes\r"
expect eof

View File

@ -0,0 +1,2 @@
---
pve_clusterinit_master: <%= @clusterinit_master %>

View File

@ -0,0 +1,6 @@
# manage the installation of a proxmox node
class roles::infra::proxmox::node {
include profiles::defaults
include profiles::base
include profiles::proxmox::init
}