Desired State Configuration #2 – Erste Schritte zur SQL Server Installation

EDIT:
Falls ihr nach einer fertigen Lösung sucht, bitte geduldet euch… ich erläutere hier meinen Projekt-Verlauf, wie ich wann wie vorgegangen bin… als letzten Blogbeitrag werde ich die „fertige“ Lösung vorstellen.

Vielen Dank für euer Verständnis

Nach meinem ersten Beitrag – den Vorbereitungen zur Nutzung von Desired State Configuration (DSC) – kann man nun die erste Schritte zur Installation eines SQL Servers einleiten. Was benötigen wir also für Voraussetzungen für die Installation eines SQL Servers auf einem neuen Windows Server?

Zu meiner Test-Umgebung… ich nutze für die Entwicklung meiner Skripte in diesem Fall eine Dev-Umgebung in Azure bestehend aus mindestens zwei Windows Servern. Ich nutze einen Server als Domänencontroller und die zweite Maschine als SQL Server, um hier meine Skripte zu testen und die Funktion zu testen. Einen oder mehrere Server kann und werde ich nach Bedarf ausrollen, um eben meine Demos zu halten bzw meine Skripte zu entwickeln und testen.

Entwicklungsumgebung für meine SQL Server Installationsversuche mit Desired State Configuration in Azure
Entwicklungs-Umgebung für Ola Hallengren und Powershell-Skripte

Voraussetzungen für die Installation

Um eine einfache SQL Server Installation – im Sinne meines Kunden-Projektes – auf einem neuen Server durchzuführen benötigen wir dreierlei Dinge:

  • .NET Framework 4.5 (Minimum)
  • Installations-Medium SQL Server 2017 Standard Edition
  • und das letzte Update im Sinne des letzten Cumulative Updates

Um das Installations-Medium auf dem neuen Server bereitzustellen, bedarf es mindestens eines Ordner auf irgendeinem Laufwerk. Nun gibt es mehrere Möglichkeiten diesen Ordner bereitzustellen…

  • manuelle Anlage des Ordners
  • Anlage des Ordners mittels DSC
  • Anlage des Ordners mittels Powershell

Da wir ins diesem Projekt, dieser Beitragsserie bestrebt sind alles zu automatisieren, fällt natürlich das manuelle Anlegen des Ordners weg. Je nach dem wie man vorgehen möchte, welche Voraussetzungen man erfüllen muss/möchte bzw in welcher Reihenfolge man vorgehen möchte, kann man nun wählen zwischen Desired State Configuration oder Powershell. In diesem Projekt möchte ich darstellen, wie man nun zur erst auf dem Zielserver ein entsprechendes Verzeichnis anlegt und dann dort mit einfachen Mitteln das Installationsmedium ablegt.

Anlage des Ordners mit Powershell

Um mein Vorhaben zu realisieren, müsste ich theoretisch erst eine Desired State-Configuration für die Folder-Anlage erstellen sowie implementieren und die Kopier-Aktion starten, dann die eigentliche Installation starten, das versuche ich erst in einem zweiten Schritt, jetzt starte ich mit der „einfacheren“ Vorgehensweise. Dazu nutze ich das „Invoke-Command“ und prüfe ob der Ordner auf dem Zielserver vorhanden ist oder nicht, wenn nicht wird der Ordner neu angelegt.

Invoke-Command -ComputerName $NodeName -ScriptBlock { 
param ($LocalInstallFolder)
if (!(Test-Path -Path $LocalInstallFolder )) {
    New-Item -ItemType directory -Path $LocalInstallFolder | Out-Null
} else {
    Remove-Item -Recurse -Force $LocalInstallFolder
    New-Item -ItemType directory -Path $LocalInstallFolder | Out-Null
} -ArgumentList "D:\$LocalInstallFolder\"

Warum lösche ich erst das Zielverzeichnis? Na klar, wenn ich das Skript mehrfach ausführe, dann muss immer das aktuelle Installationsmedium und das aktuelle Update dort bereit liegen, daher wird erst einmal das Verzeichnis gelöscht, um dann neu erstellt zu werden.
Wenn die Ziel-Verzeichnisse vorhanden sind, dann kann man die benötigeten Dateien/Verzeichnisse kopieren.

Kopieren des Installationsmediums

Anfänglich hatte ich meine Dateien alle mit „Copy-Item“ vom Netzlaufwerk auf den Zielserver kopiert, da ich aber doch recht viel mit der Powershell ISE entwickel, fehlte mir eine „Fortschrittsanzeige“… daher bin ich später auf „Start-BitsTransfer“ umgestiegen.

Write-host "Copy SQL-Image to"$NodeName.ToUpper()
$DestinationPath = "\\$NodeName\d$\$LocalInstallFolder\"
Start-BitsTransfer -Source ..\SQL\* -Destination $DestinationPath -Description "..\SQL\* will be moved to $DestinationPath" -DisplayName "Copy SQL-Image" 
Start-BitsTransfer -Source ..\SQL\Updates\* -Destination "$DestinationPath\Updates\" -Description "..\SQL\Updates\* will be moved to $DestinationPath" -DisplayName "Copy SQL-Updates" 

Aber das war mir dann irgendwie auch zu aufwendig und kompliziert, so irgendwie „Work-around“… aber zu mindestens funktionierte es 😉 Genauso bin ich vorgegangen, als die notwendigen Powershell-Module auf den Zielserver kopiert habe. Aber mit jedem Tag und jedem weiteren Versuch sich mit Desired State Configuration auseinanderzusetzen, lernt man dazu.
So bin ich jetzt dazu übergegangen auch dieses vorbereitende Kopieren mittels DSC zu realisieren.

Configuration CopyInstallationMedia
{
    Node $AllNodes.where{ $_.Role.Contains("SqlServer") }.NodeName
    {
        File InstallationFolder 
        {
            Ensure = 'Present'
            Type = 'Directory'
            SourcePath = "\\dc1\NetworkShare\SQL\"
            DestinationPath = "D:\SQL2017\"
            Recurse = $true
        }
    
        File PowershellModules 
        {
            Ensure = 'Present'
            Type = 'Directory'
            SourcePath = "\\dc1\NetworkShare\Modules\"
            DestinationPath = "C:\Windows\system32\WindowsPowerShell\v1.0\Modules\"
            Recurse = $true
        }
    }
}

Clear-Host
$OutputPath = "\\dc1\DSC-ConfigShare"
CopyInstallationMedia -OutputPath "$OutputPath\CopyInstallationMedia\"    

Start-DscConfiguration -ComputerName sqlvm02 -Path \\DC1\DSC-ConfigShare\CopyInstallationMedia -Wait -Verbose -Force

Aufbau meiner DSC-Konfigurationen

Ich habe mir zwecks besserer Übersicht und vereinfachtem, schrittweisem Ausprobieren sowie Nachvollziehbarkeit mehrere Abschnitte in meiner Desired-State-Configuration angelegt. Die jeweilige Konfiguration kann ich nun gezielt und einzeln aufrufen und „verfeinern“.

Configuration CopyInstallationMedia
{
    Node $AllNodes.where{ $_.Role.Contains("SqlServer") }.NodeName
    {
        File InstallationFolder 
        {
            ...
        }

        File PowershellModules 
        {
            ...
        }

        Configuration ConfigureSQL
        {
            ...
        }
}

Natürlich bedarf es auch einiger Parameter, die man nicht ungeachtet lassen darf, diese werden eingangs des Skriptes definiert. In meinen Fall benötige ich zur Installation und späteren Konfiguration zumindest den Pfad wo die Installationsmedien zentral abgelegt werden und den Zielserver, wo die DSC-Konfiguration ausgerollt werden soll.

param
(
    # Path where all Install media will be located
    [Parameter(Mandatory=$true)]
    [String]
    $InstallPath,

    # Computer name to install SQL Server On
    [Parameter(Mandatory=$true)]
    [String]
    $ComputerName
)

Nachdem ich nun die Konfiguration unterteilt und definiert habe, kann ich notwendige Skripte bzw Module hinzufügen und das Erstellen der MOF-Files einleiten. Anhand dieser MOF-Files wird dann die IST-Konfiguration mit der SOLL-Konfiguration abgeglichen und entsprechend korrigiert. Da sich zwischen letztmaligen Erstellen der MOF-Files und „Jetzt“ etwas an der SOLL-Konfiguration geändert haben könnte, lasse ich sicherheitshalber immer die Files neu erstellen, um diese direkt im Anschluss auf den Zielserver auszurollen. Zur näheren Erläuterung meines Skript-Abschnittes… Ich rufe die jeweilige Konfiguration auf, weise ihr eine Konfigurationsdatei hinzu und definiere einen Pfad für die Ablage der MOF-Files. Als Abschluss wird die jeweilige SOLL-Konfiguration vom zentralen Ablageort auf den Zielserver ausgerollt.
Wie man nun auch nachvollziehen kann, bin ich in der Lage die einzelnen Schritte auch separat für Debug-Zwecke auszuführen, entweder manuell nacheinander oder nur einzelne Konfigurationen wie zum Beispiel die bloße Konfiguration eines SQL Servers.

Write-host "Starting DSC process on"$NodeName.ToUpper()
Import-Module $PSScriptRoot\ConfigureSQLServer.psm1 -Force

## Create MOF-Files
$OutputPath = "\\dc1\DSC-ConfigShare"
CopyInstallationMedia -ConfigurationData \\dc1\NetworkShare\scripts\configData.psd1 -OutputPath "$OutputPath\CopyInstallationMedia\"
SQLInstall -ConfigurationData \\dc1\NetworkShare\scripts\configData.psd1 -OutputPath "$OutputPath\SQLInstall\"
ConfigureSQL -ConfigurationData \\dc1\NetworkShare\scripts\configData.psd1 -OutputPath "$OutputPath\SQLConfig\"

## Use MOF-Files to establish desired state configuration
Start-DscConfiguration -ComputerName $Computername -Path \\DC1\DSC-ConfigShare\CopyInstallationMedia -Wait -Verbose -Force     
Start-DscConfiguration -ComputerName $Computername -Path \\DC1\DSC-ConfigShare\SQLInstall -Wait -Verbose -Force     
Start-DscConfiguration -ComputerName $Computername -Path \\DC1\DSC-ConfigShare\SQLConfig -Wait -Verbose -Force

So sieht dann meine Ordner-Struktur bzw Datei-Struktur auf dem zentralen Server aus, für jede SOLL-Konfiguration gibt es einen Ordner und für jeden Zielserver eine einzelne Datei.

Mehr zum Thema Desired State Configuration natürlich in der Microsoft Dokumentation

#3 Firewall Ports für den SQL Server öffnen mit Powershell

Nach ein paar Tagen Ruhe kommt nun mein dritter Teil der Powershell Serie zum Thema „Firewall Ports öffnen„.
Sicherlich wird der ein oder andere sagen, „Firewall? die interne? die haben wir per Gruppenrichtlinie immer ausgeschaltet!“…was ist vielleicht in einem halben Jahr, jemand aktiviert die Windows Firewall auf dem Server manuell? Kann der SQL Server (also die Datenbank-Enigne) dann immer noch mit der Welt da draußen kommunizieren?

Sicherheit geht vor – daher nur definiert und bewußt die Firewall Ports öffnen

Windows Firewall Ports öffnen für den SQL Server

Ich empfehle nur die notwendigen Ports für die Datenbank-Enigne zu öffnen, im Beispielbild wären das die folgenden Ports

  • SQL Server DB Engine => TCP 10001
  • der SQL Browser => UDP 1434
  • der SQL Service => Broker 4022

Dies reicht völlig aus, um dem SQL Server und den nutzenden Applikationen im Notfall eine Kommunikation zu ermöglichen. Nun gibt es mehrere Möglichkeiten dies zu realisieren, die Konfiguration der Firewall über die grafische Oberfläche, was doch umständlich und „kompliziert“ sein kann, aber ebenso der Weg über die Kommandozeile entweder als DOS-Befehl bzw mit dem netsh-Befehl oder mit einem Powershell-Skript.

Wer noch andere Features des SQL Servers auf dem selben Server einsetzt, muss natürlich noch weitere Ports öffnen und auch die entsprechenden Skripte erweitern, aber in der Regel reichen diese Firewall Ports für einen Datenbank Server aus.

weitere Hinweise zu den Firewall Ports des SQL Server findet man hier : https://msdn.microsoft.com/de-de/library/cc646023.aspx

Umsetzung über die Kommandozeile

netsh advfirewall firewall add rule name="SQL Server (TCP 10001) TEST_Instanz" dir=in action=allow protocol=TCP localport=10001 profile=domain
netsh advfirewall firewall add rule name="SQL Service Broker (TCP 4022)" dir=in action=allow protocol=TCP localport=4022 profile=domain
netsh advfirewall firewall add rule name="SQL Browser (UDP 1434)" dir=in action=allow protocol=UDP localport=1434 profile=domain

Der netsh-Befehl lässt sich sehr gut parametrisieren und skripten, ist daher sehr individuell einsetzbar, also recht umgänglich. Hier kommt es auf die Unternehmensstrategie oder die der Systemadministratoren an, welche Skriptsprache bevorzugt wird. Ich bevorzuge (mittlerweile) Powershell, was auch Hauptthema dieser Serie ist, daher gehe ich nun etwas tiefer in die Umsetzung „Firewall Ports öffnen“ mit Powershell ein.

Umsetzung mittels Powershell

Auch die Windows-Firewall hat ein Powershell Commandlet, so dass wir die Firewall Ports recht einfach und komfortabel öffnen können. Mein Lösungsansatz ist vielleicht etwas länger und umfangreicher, der erfahrene Powershell Programmierer bekommt das bestimmt auch in eine Schleife gepackt oder gar in eine Zeile 😉
Meine Zeilen stammen aus einem Skript, welches ich für die Konfiguration nach der Installation verwende, daher sind hier zahlreiche Variablen genutzt worden, um die Flexibilität zu erhalten.

  • $SQLServerTCPPort = 10001
  • $InstanceName = TEST_Instanz

Auch ein wenig „Monitoring“ habe ich implementiert, so dass man gleich das Ergebnis, den Fehlschlag bzw den Fortschritt anhand der Ausgabe erkennen kann. Für meine Zwecke im Rahmen der SQL Server Installation reichen diese Zeilen um die Firewall Ports zu öffnen, zudem erklären sich die einzelnen Parameter nahezu von selbst.

Write-Host "Opening Firewall ports for this Instance"
    Try {
		Write-Host "Opening Firewall on Port $SQLServerTCPPort" 
		$port1 = New-Object -ComObject HNetCfg.FWOpenPort
		$port1.Port = $SQLServerTCPPort
		$port1.Name = "SQL Server (TCP " + $SQLServerTCPPort + ") " + $InstanceName 
		$port1.Enabled = $true
		$port1.Protocol = 6
		$fwMgr = New-Object -ComObject HNetCfg.FwMgr
		$profiledomain=$fwMgr.LocalPolicy.GetProfileByType(0)
		$profiledomain.GloballyOpenPorts.Add($port1)
        Write-Host "[INFO] Successfully opened Firewall on Port $SQLServerTCPPort." -ForegroundColor Green
     } 
    Catch { 
        Write-Host "[ERROR] Opening Firewall on Port $SQLServerTCPPort failed." -ForegroundColor Red 
    }
   
   Try {
		Write-Host "Opening Firewall on Port 4022" 
		$port1 = New-Object -ComObject HNetCfg.FWOpenPort
		$port1.Port = 4022
		$port1.Name = "SQL Service Broker (TCP 4022)"
		$port1.Enabled = $true
		$port1.Protocol = 6
		$fwMgr = New-Object -ComObject HNetCfg.FwMgr
		$profiledomain=$fwMgr.LocalPolicy.GetProfileByType(0)
		$profiledomain.GloballyOpenPorts.Add($port1) 
        Write-Host "[INFO] Successfully opened Firewall on Port 4022." -ForegroundColor Green
    } 
    Catch { 
        Write-Host "[ERROR] Opening Firewall on Port 4022 failed." -ForegroundColor Red 
    }
   
    Try {
		Write-Host "Opening Firewall on Port UDP 1434" 
		$port1 = New-Object -ComObject HNetCfg.FWOpenPort
		$port1.Port = 1434
		$port1.Name = "SQL Browser (UDP 1434)"
		$port1.Enabled = $true
		$port1.Protocol = 17
		$fwMgr = New-Object -ComObject HNetCfg.FwMgr
		$profiledomain=$fwMgr.LocalPolicy.GetProfileByType(0)
		$profiledomain.GloballyOpenPorts.Add($port1) 
        Write-Host "[INFO] Successfully opened Firewall on Port UDP 1434." -ForegroundColor Green
    } 
    Catch { 
        Write-Host "[ERROR] Opening Firewall on Port 1434 failed." -ForegroundColor Red 
    }

#2 Disable NIC Power Save Mode mit Powershell

Weiter geht es mit dem zweiten Teil meiner Powershell-Reihe:
Diesmal möchte ich den Power Save Mode der Netzwerkkarten abschalten, damit diese Funktion nicht „plötzlich“ zum Verhängnis wird. In Verbindung mit dem „Balanced“-Powerplan kann Windows der Meinung sein, dass die Netzwerkkarte bei Inaktivität in den „Sleep“-Mode versetzt wird. Somit verliert der SQL Server plötzlich seine (inaktiven) Connections, zahlreiche Applikationen werden diese Trennungen nicht mögen. Noch viel schlimmer in einem Cluster oder mit AlwaysOn Availability-Groups, plötzlich ist der Heartbeat weg und das Cluster schwenkt.

Widersprüchliches in High Performance

Auch wenn man meinen würde, dass dieser Power Save Mode der Netzwerkkarte abgeschaltet wird, wenn man den Powerplan auf „High Performance“ setzt. Nein, laut Aussage eines Microsoft PFE Team-Members (Oktober 2016) hilft auch dies nicht:

• If the server is set to High Performance – Windows places the system in the highest performance state and disables the dynamic scaling of performance in response to varying workload levels. Therefore, special care should be taken before setting the power plan to High Performance as this can increase power consumption unnecessarily when the system is underutilized.
• We will have to disable “Allow the computer to turn off this device to save power” option of Power management from NIC. Setting the server in High Performance would not stop the NIC to go in sleep mode whenever there is no activity, as that is a setting at an OS level. The setting on NIC will take preference in this situation.

Somit hilft nur das Abschalten dieses Power Save Modes an allen Netzwerkkarten!

Disable NIC Power Save Mode

Diese Powershell-Lösung habe ich mir nicht selber ausgedacht, mit dieser hatte ich die wenigsten Probleme, sie funktionierte auf zahlreichen Maschinen.
Die Lösung von Ingmar Verheij habe ich angepasst von WiFi auf LAN-Adapter gemäß iana.org, lässt sich sicherlich auch auf andere Adapter anpassen.

Was macht das Powershell Snippet?

Das Skript sucht sich alle installierten Netzwerkkarten und überprüft, ob diese vom Typ „6“ (ethernetCsmacd(6), for all ethernet-like interfaces, regardless of speed, as per RFC3635) sind. Wurde eine entsprechende Ethernet-Netzwerkkarte gefunden wird für diese in der Registry der Wert entsprechend geändert, so dass die Netzwerkkarte nicht mehr in den Power Save Mode wechseln kann.

$intNICid=0; do {
     #Read network adapter properties
     $objNICproperties = (Get-ItemProperty -Path ("HKLM:\SYSTEM\CurrentControlSet\Control\Class\{0}\{1}" -f "{4D36E972-E325-11CE-BFC1-08002BE10318}", ( "{0:D4}" -f $intNICid)) -ErrorAction SilentlyContinue)
 
     #Determine if the Network adapter index exists 
     If ($objNICproperties) {
          #Filter network adapters
          # * only Ethernet adapters (ifType = ieee80211(6) - http://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib)
          # * root devices are exclude (for instance "WAN Miniport*")
          # * software defined network adapters are excluded (for instance "RAS Async Adapter")
          If (($objNICproperties."*ifType" -eq 6) -and ($objNICproperties.DeviceInstanceID -notlike "ROOT\*") -and ($objNICproperties.DeviceInstanceID -notlike "SW\*")) {
               #Read hardware properties
               $objHardwareProperties = (Get-ItemProperty -Path ("HKLM:\SYSTEM\CurrentControlSet\Enum\{0}" -f $objNICproperties.DeviceInstanceID) -ErrorAction SilentlyContinue)
               If ($objHardwareProperties.FriendlyName) {
                    $strNICDisplayName = $objHardwareProperties.FriendlyName
               } else { 
                    $strNICDisplayName = $objNICproperties.DriverDesc
               }
               #Read Network properties
               $objNetworkProperties = (Get-ItemProperty -Path ("HKLM:\SYSTEM\CurrentControlSet\Control\Network\{0}\{1}\Connection" -f "{4D36E972-E325-11CE-BFC1-08002BE10318}", $objNICproperties.NetCfgInstanceId) -ErrorAction SilentlyContinue)

               #Inform user
               Write-Host -NoNewline -ForegroundColor White " ID : "; Write-Host -ForegroundColor Yellow ( "{0:D4}" -f $intNICid)
               Write-Host -NoNewline -ForegroundColor White " Network: "; Write-Host $objNetworkProperties.Name
               Write-Host -NoNewline -ForegroundColor White " NIC : "; Write-Host $strNICDisplayName
               Write-Host -ForegroundColor White " Actions:"

               #Disable power saving
               Set-ItemProperty -Path ("HKLM:\SYSTEM\CurrentControlSet\Control\Class\{0}\{1}" -f "{4D36E972-E325-11CE-BFC1-08002BE10318}", ( "{0:D4}" -f $intNICid)) -Name "PnPCapabilities" -Value "24" -Type DWord
                Write-Host -ForegroundColor Green (" - Power saving disabled")
                Write-Host ""
           }
      } 
      #Next NIC ID
      $intNICid+=1
} while ($intNICid -lt 255)

mySQL Installation – Version 5.7.1 unter RedHat Linux 6

Bei der mySQL Installation musste ich erst einige Startschwierigkeiten beseitigen, bevor ich nun auch mit der Konfiguration anfangen konnte…
Ein großer Dank gilt meinem Linux-Kollegen, der mir durch die Schwierigkeiten der RPM-Abhängigkeiten geholfen hat.

Also kurz zur Erläuterung, die Linux-Kollegen haben in Ihrem Standard-Image immer Postfix enthalten, leider beinhaltet dieses Postfix Paket auch die mySQL-Libs in einer Uralt-Version 5.1.73. Welche sich nicht so einfach entfernen oder aktualisieren lassen…
RPM -U hat uns dann letztendlich – nach der Beseitigung anderer technischer Probleme – geholfen und wir konnten alle Pakete der mySQL Installation sauber installieren bzw aktualisieren, so dass wir nun folgenden Stand haben.

rpm -qa | grep mysql
mysql-community-server-5.7.9-1.el6.x86_64
mysql-community-common-5.7.9-1.el6.x86_64
mysql-community-libs-compat-5.7.9-1.el6.x86_64
mysql-community-libs-5.7.9-1.el6.x86_64
mysql-community-client-5.7.9-1.el6.x86_64

Dies ist unsere Ausgangslage und kommen somit nun zur Konfiguration und Einrichtung:

Gemäß Dokumentation auf den mySQL Developer Seiten findet sich nach der Installation folgende Datei-Strukur auf dem Server:

 

Files or Resources

Location
Client programs and scripts /usr/bin
mysqld server /usr/sbin
Configuration file /etc/my.cnf
Data directory /var/lib/mysql
Error log file For RHEL, Oracle Linux, CentOS or Fedora platforms: /var/log/mysqld.log

For SLES: /var/log/mysql/mysqld.log

Value of secure_file_priv /var/lib/mysql-files
System V init script For RHEL, Oracle Linux, CentOS or Fedora platforms: /etc/init.d/mysqld

For SLES: /etc/init.d/mysql

Systemd service For RHEL, Oracle Linux, CentOS or Fedora platforms: mysqld

For SLES: mysql

Pid file /var/run/mysql/mysqld.pid
Unix manual pages /usr/share/man
Include (header) files /usr/include/mysql
Libraries /usr/lib/mysql
Socket /var/lib/mysql/mysql.sock
Miscellaneous support files (for example, error messages and character set files) /usr/share/mysql

Die mySQL Konfigurationdatei my.cnf finden wir also im Ordner /etc und hat dann auf einem RedHat 6 System folgende standardmäßigen Inhalt:

# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html

[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

Dieser Inhalt kann selbstverständlich jederzeit an die eigenen Bedürfnisse angepasst werden.

Nach erfolgreicher mySQL Installation muss man nun die Datenbank erstmalig starten:

service mysqld start

Initializing MySQL database:         [ OK ]
Starting mysqld:                     [ OK ]

Im Rahmen der mySQL Installation und dem Starten der Engine wurde auch ein mySQL-Root User (‚root’@’localhost‘) angelegt, mittels des Befehls

grep 'temporary password' /var/log/mysqld.log

kann man sich nun das automatisch vergebene Password anzeigen lassen. der erste Schritt zu einer sicheren mySQL Installation ist nun sich anzumelden und dass Passwort des root-Users zu ändern, hierzu melden wir uns an der Datenbank-Engine an:

mysql -uroot -p
ALTER USER 'root'@'localhost' IDENTIFIED BY 'MeinAdminPasswort2015!';

Achtung!
Standardmäßig ist das „Validate Password“-Plugin installiert/aktiviert, dadurch müssen Passwörter eine gewisse Komplexibiliät aufweisen.
Passwörter müssen mindestens einen Großbuchstaben, einen Kleinbuchstaben, eine Ziffer und ein Sonderzeichen enthalten und das Passwort muss eine Gesamtlänge von mindestens 8 Zeichen aufweisen.

Im Vergleich zu alten mySQL Installationen muss die neue Datenbank Engine nicht extra (mittels mysql_secure_installation-Skript ) gesichert werden, heißt es gibt keine test-Datenbank mehr und auch keinen Anonymen Login. Das Skript an sich gibt es noch und kann nach wie vor die mySQL Installation sicher(er) machen.

automatisches Starten des Dienste beim Systemstart
Damit nun der mySQL Datenbankserver beim Booten automatisch gestartet wird, nehmen wir noch folgende Konfigurationsschritte vor und überprüfen anschließend das Ergebnis (welches dann so aussehen sollte).

chkconfig mysqld on

chkconfig --list | grep mysqld
mysqld 0:off 1:off 2:on 3:on 4:on 5:on 6:off

Nun bin ich im Grunde auch schon durch mit der mySQL-Installation und Einrichtung. Ab jetzt kann der mySQL Server genutzt werden und für die jeweiligen Applikationen User und Datenbanken angelegt werden.

ABER um einen stabilen Betrieb für einen mySQL-Server gewährleisten zu können, fehlen zumindest noch zwei wichtige Themen:

  • Log-Rotation
  • Backup

Log-Rotation
Auf kleinen mySQL-Maschinen fällt es vielleicht nicht so sehr auf, aber auf etwas größeren mySQL Installationen (auf denen mehr Last ist bzw die Datenbanken größer sind) werden die Logs schnell unübersichtlich und schwer lesbar. Daher empfehle ich/wir die Nutzung eines Skriptes, welches die Logs in regelmäßigen Abständen „abschneidet“ und somit „klein“ hält. In früheren mySQL Server Versionen wurde kein Skript für eben dieses Log-Rotate mitgeliefert, mittlerweile hat sich dies geändert.

Meine RedHat6 mySQL Installation hat bereits eigenständig eine Datei unter /etc/logrotate.d abgelegt und somit wird automatisch ein entsprechendes Aufrufen, Abschneiden der Logs initiert, zusätzlich befindet sich dieses Skript im Ordner /usr/share/mysql.

# The log file name and location can be set in
# /etc/my.cnf by setting the "log-error" option
# in either [mysqld] or [mysqld_safe] section as
# follows:
#
# [mysqld]
# log-error=/var/lib/mysql/mysqld.log
#
# In case the root user has a password, then you
# have to create a /root/.my.cnf configuration file
# with the following content:
#
# [mysqladmin]
# password =  
# user= root
#
# where "" is the password. 
#
# ATTENTION: The /root/.my.cnf file should be readable
# _ONLY_ by root !

/var/lib/mysql/mysqld.log {
        # create 600 mysql mysql
        notifempty
        daily
        rotate 5
        missingok
        compress
    postrotate
	# just if mysqld is really running
	if test -x /usr/bin/mysqladmin && \
	   /usr/bin/mysqladmin ping &>/dev/null
	then
	   /usr/bin/mysqladmin flush-logs
	fi
    endscript
}

Backup
Natürlich müssen mySQL Installationen und deren Datenbanken auch regelmäßig gesichert werden, hierzu liefert mySQL ein Tool namens mysqldump mit, welches sich sehr gut für die meisten Systeme und Anforderungen eignet.

Wir starten aber erst einmal damit einen Backup-User anzulegen, dieser erhält nur Rechte um Backups auszuführen.

CREATE USER 'backup'@'localhost' IDENTIFIED BY 'secret';
GRANT SELECT, SHOW VIEW, RELOAD, REPLICATION CLIENT, EVENT, TRIGGER ON *.* TO 'backup'@'localhost';
GRANT LOCK TABLES ON *.* TO 'backup'@'localhost';
FLUSH PRIVILEGES;

Dann benötigen wir ein Skript, welches uns das backup erstellt… Hierzu haben wir zweierlei Möglichkeiten

Kurz und knapp

num=$(( ($(date "+%H") + 4 ) / 4))
/usr/bin/mysqldump -u DBUSERNAME -pDBPASSWORD DBNAME > /PATH/backup${num}.sql

hier gefunden (Dank an fedorqui)

oder etwas ausführlicher (Dank an Django für sein großartiges mySQL Backup-Skript)

#!/bin/bash

##################################################################################
# Script-Name : mysqldump.sh #
# Description : Datenbank-Dump der kompletten (alle Tabellen) unserer #
# MySQL-Datenbank nach /root/mysql/dumps #
# Drei Datensicherungen werden aufgehoben, ältere werden gelöscht. #
# #
# #
# #
# Last update : 13.05.2013 #
# Version : 0.01 #
##################################################################################

##################################################################################
# H I S T O R Y #
##################################################################################
# Version : 0.01 #
# Description : initial release #
# ------------------------------------------------------------------------------ #
# Version : x.xx #
# Description : <Description> #
##################################################################################

# Source function library.
. /etc/init.d/functions

# Definition der systemindividuellen Variablen

# Script-Name.
SCRIPT_NAME='mysqldump'

# Backup-Verzeichnis.
DIR_TARGET='/var/lib/mysql/backup'
DUMP_FILES="$DIR_TARGET/*.sql"

# Mail-Empfänger
MAIL_RECIPIENT='dlde-ats-dbs-mysql@atos.net'

# Status-Mail versenden? [J|N].
MAIL_STATUS='N'

# Datenbankdefinitionen
DB_HOST="127.0.0.1"
DB_USER="backup"
DB_SECRET="ccQPFMN7Yc6rmiGn!"

# Variablen
MYSQLDUMP_COMMAND=`command -v mysqldump`
TOUCH_COMMAND=`command -v touch`
RM_COMMAND=`command -v rm`
PROG_SENDMAIL=`command -v sendmail`
CAT_COMMAND=`command -v cat`
DATE_COMMAND=`command -v date`
MKDIR_COMMAND=`command -v mkdir`
FILE_NAME='/'$SCRIPT_NAME'.'`$DATE_COMMAND '+%Y-%m-%d-%H%M%S'`'.sql'
FILE_LOCK='/tmp/'$SCRIPT_NAME'.lock'
FILE_LOG='/var/log/'$SCRIPT_NAME'.log'
FILE_LAST_LOG='/tmp/'$SCRIPT_NAME'.log'
FILE_MAIL='/tmp/'$SCRIPT_NAME'.mail'
VAR_HOSTNAME=`uname -n`
VAR_SENDER='root@'$VAR_HOSTNAME
VAR_EMAILDATE=`$DATE_COMMAND '+%a, %d %b %Y %H:%M:%S (%Z)'`

# Functionen
function log() {
echo $1
echo `$DATE_COMMAND '+%Y/%m/%d %H:%M:%S'` " INFO:" $1 >>${FILE_LAST_LOG}
}

function movelog() {
$CAT_COMMAND $FILE_LAST_LOG >> $FILE_LOG
$RM_COMMAND -f $FILE_LAST_LOG
$RM_COMMAND -f $FILE_LOCK
}

function sendmail() {
case "$1" in
'STATUS')
MAIL_SUBJECT='Status execution '$SCRIPT_NAME' script.'
;;
*)
MAIL_SUBJECT='ERROR while execution '$SCRIPT_NAME' script !!!'
;;
esac

$CAT_COMMAND <<MAIL >$FILE_MAIL
Subject: $MAIL_SUBJECT
Date: $VAR_EMAILDATE
From: $VAR_SENDER
To: $MAIL_RECIPIENT

MAIL

$CAT_COMMAND $FILE_LAST_LOG >> $FILE_MAIL

$PROG_SENDMAIL -f $VAR_SENDER -t $MAIL_RECIPIENT < $FILE_MAIL

$RM_COMMAND -f $FILE_MAIL

}

# Main.
log ""
log "+-------------------------------------------------------------------------------+"
log "| .................... Start des MySQL-Datenbank-Dumps ........................ |"
log "+-------------------------------------------------------------------------------+"
log ""
log "Das Datenbank-Backupscript wurde mit folgenden Parametern aufgerufen:"
log ""
log "SCRIPT_NAME : $SCRIPT_NAME"
log "ZIEL-VERZEICHNIS: $DIR_TARGET"
log "MAIL_EMPFÄNGER : $MAIL_RECIPIENT"
log "MAIL_STATUS : $MAIL_STATUS"
log ""

# Prüfung ob alle benötigten Programme und Befehle vorhanden sind.
if [ ! -s "$MYSQLDUMP_COMMAND" ]; then
log "Prüfen, ob das Programm '$MYSQLDUMP_COMMAND' vorhanden ist.................[FEHLER]"
sendmail ERROR
movelog
exit 10
else
log "Prüfen, ob das Programm '$MYSQLDUMP_COMMAND' vorhanden ist.................[ OK ]"
fi

if [ ! -s "$TOUCH_COMMAND" ]; then
log "Prüfen, ob das Programm '$TOUCH_COMMAND' vorhanden ist.........................[FEHLER]"
sendmail ERROR
movelog
exit 11
else
log "Prüfen, ob das Programm '$TOUCH_COMMAND' vorhanden ist.........................[ OK ]"
fi

if [ ! -s "$RM_COMMAND" ]; then
log "Prüfen, ob das Programm '$RM_COMMAND' vorhanden ist............................[FEHLER]"
sendmail ERROR
movelog
exit 12
else
log "Prüfen, ob das Programm '$RM_COMMAND' vorhanden ist............................[ OK ]"
fi

if [ ! -s "$CAT_COMMAND" ]; then
log "Prüfen, ob das Programm '$CAT_COMMAND' vorhanden ist..........................[FEHLER]"
sendmail ERROR
movelog
exit 13
else
log "Prüfen, ob das Programm '$CAT_COMMAND' vorhanden ist...........................[ OK ]"
fi

if [ ! -s "$DATE_COMMAND" ]; then
log "Prüfen, ob das Programm '$DATE_COMMAND' vorhanden ist...........................[FEHLER]"
sendmail ERROR
movelog
exit 14
else
log "Prüfen, ob das Programm '$DATE_COMMAND' vorhanden ist..........................[ OK ]"
fi

if [ ! -s "$MKDIR_COMMAND" ]; then
log "Prüfen, ob das Programm '$MKDIR_COMMAND' vorhanden ist..........................[FEHLER]"
sendmail ERROR
movelog
exit 15
else
log "Prüfen, ob das Programm '$MKDIR_COMMAND' vorhanden ist.........................[ OK ]"
fi

if [ ! -s "$PROG_SENDMAIL" ]; then
log "Prüfen, ob das Programm '$PROG_SENDMAIL' vorhanden ist.................[FEHLER]"
sendmail ERROR
movelog
exit 16
else
log "Prüfen, ob das Programm '$PROG_SENDMAIL' vorhanden ist.................[ OK ]"
fi

if [ ! -e "$FILE_LOCK" ]; then
log "Prüfen, ob das Programm nicht bereits oder noch läuft......................[ OK ]"

$TOUCH_COMMAND $FILE_LOCK
else
log "Prüfen, ob das Programm nicht bereits oder noch läuft......................[FEHLER]"
log ""
log "FEHLER: Das Script läuft bereits bzw. immer noch, oder die LOCK-Datei"
log "existiert noch von einem früheren Programmaufruf!"
log ""
sendmail ERROR
movelog
exit 20
fi

if [ ! -d "$DIR_TARGET" ]; then
log "Prüfen, ob Zielverzeichnis existiert.......................................[FEHLER]"
log ""
log " INFO: Erstelle Zielverzeichnis!"
log " INFO: --> "$DIR_TARGET
log ""

$MKDIR_COMMAND -p $DIR_TARGET
else
log "Prüfen, ob Zielverzeichnis existiert.......................................[ OK ]"
fi

if [ "$UID" -ne 0 ]; then
log "Prüfen, ob das Script mit root-Rechten gestartet wurde.......................[FEHLER]"
log ""
sendmail ERROR
movelog
exit 21
else
log "Prüfen, ob das Script mit root-Rechten gestartet wurde.....................[ OK ]"
fi

# Start dumping.
log ""
log "+-------------------------------------------------------------------------------+"
log "| .................... Start des Datenbank-Dumps .............................. |"
log "+-------------------------------------------------------------------------------+"
log ""

log "$MYSQLDUMP_COMMAND -h "$DB_HOST" -u "$DB_USER" --all-databases --events > $DIR_TARGET$FILE_NAME"

$MYSQLDUMP_COMMAND -h $DB_HOST -u $DB_USER --password=$DB_SECRET --all-databases --events > $DIR_TARGET$FILE_NAME

if [ "$?" != 0 ]; then
log ""
$RM_COMMAND -f $FILE_LOCK
sendmail ERROR
movelog
exit 99
else
log ""
log "+-------------------------------------------------------------------------------+"
log "| ........................ Datenbank-Dump beendet ............................. |"
log "+-------------------------------------------------------------------------------+"
log ""
fi

# Bis auf die letzten drei Datenbankbackups alle anderen Dateien löschen.
cd $DIR_TARGET/
(ls $DUMP_FILES -t|head -n 3;ls $DUMP_FILES )|sort|uniq -u|xargs rm
if [ "$?" != "0" ]; then
log "alte Datenbanksicherungen aus Zielverzeichnis $DIR_TARGET gelöscht....[FEHLER]"
log ""
sendmail ERROR
movelog
exit 69
else
log "alte Datenbanksicherungen aus Zielverzeichnis $DIR_TARGET gelöscht....[ OK ]"
log ""
fi

# Finish syncing.
log "+-------------------------------------------------------------------------------+"
log "| ..................... Ende des MySQL-Datenbank-Dumps ........................ |"
log "+-------------------------------------------------------------------------------+"
log ""

# Status eMail versenden
if [ $MAIL_STATUS = 'J' ]; then
sendmail STATUS
fi

# Temporäres Logfile permanent sichern
movelog

exit 0

Jetzt nur noch einen passenden Cronjob dafür erstellen, wann auch immer das Backup erstellt werden soll.
Und natürlich alles testen. 😉