diff --git a/README.md b/README.md index fb210d3..b4917a4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ -# moby - +# Эксперименты с Windows Containers + +## Установка Docker (Moby) + +Требования по ОС включают в себя Windows Server или Windows 10/11 Pro. + +1. Конфигурируем выполнение сценариев ([PowerShell execution policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.5)) +``` +Get-ExecutionPolicy -List +Get-ExecutionPolicy -Scope CurrentUser +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +``` + +2. Выполняем [установку](https://learn.microsoft.com/en-us/virtualization/windowscontainers/quick-start/set-up-environment?tabs=dockerce): +``` +Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/microsoft/Windows-Containers/Main/helpful_tools/Install-DockerCE/install-docker-ce.ps1" -o install-docker-ce.ps1 +.\install-docker-ce.ps1 +``` + +3. Проверяем устнановку docker: +``` +PS C:\WINDOWS\system32> docker info +Client: + Version: 28.2.2 + Context: default + Debug Mode: false + +Server: + Containers: 0 + Running: 0 + Paused: 0 + Stopped: 0 + Images: 0 + Server Version: 28.2.2 + Storage Driver: windowsfilter + Windows: + Logging Driver: json-file + Plugins: + Volume: local + Network: ics internal l2bridge l2tunnel nat null overlay private transparent + Log: awslogs etwlogs fluentd gcplogs gelf json-file local splunk syslog + CDI spec directories: + /etc/cdi + /var/run/cdi + Swarm: inactive + Default Isolation: hyperv + Kernel Version: 10.0 26100 (26100.1.amd64fre.ge_release.240331-1435) + Operating System: Microsoft Windows Version 24H2 (OS Build 26100.4351) + OSType: windows + Architecture: x86_64 + CPUs: 16 + Total Memory: 29.87GiB + Name: Kubernetes + ID: c5828016-f9e0-42ab-9b7c-a64742681a38 + Docker Root Dir: C:\ProgramData\docker + Debug Mode: false + Experimental: false + Insecure Registries: + ::1/128 + 127.0.0.0/8 + Live Restore Enabled: false + Product License: Community Engine +``` + +4. Пуллим [базовые](https://learn.microsoft.com/ru-ru/virtualization/windowscontainers/manage-containers/container-base-images) образы windows: +``` +docker pull mcr.microsoft.com/windows/servercore:ltsc2025 +docker pull mcr.microsoft.com/windows/servercore:ltsc2022 +docker pull mcr.microsoft.com/windows/servercore:ltsc2019 +``` \ No newline at end of file diff --git a/scripts/install-docker-ce.ps1 b/scripts/install-docker-ce.ps1 new file mode 100644 index 0000000..df0ff7a --- /dev/null +++ b/scripts/install-docker-ce.ps1 @@ -0,0 +1,775 @@ + +############################################################ +# Script to install the community edition of docker on Windows +############################################################ + +<# + .NOTES + Copyright (c) Microsoft Corporation. All rights reserved. + + Use of this sample source code is subject to the terms of the Microsoft + license agreement under which you licensed this sample source code. If + you did not accept the terms of the license agreement, you are not + authorized to use this sample source code. For the terms of the license, + please see the license agreement between you and Microsoft or, if applicable, + see the LICENSE.RTF on your install media or the root of your tools installation. + THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES. + + .SYNOPSIS + Installs the prerequisites for creating Windows containers + + .DESCRIPTION + Installs the prerequisites for creating Windows containers + + .PARAMETER DockerPath + Path to Docker.exe, can be local or URI + + .PARAMETER DockerDPath + Path to DockerD.exe, can be local or URI + + .PARAMETER DockerVersion + Version of docker to pull from download.docker.com - ! OVERRIDDEN BY DockerPath & DockerDPath + + .PARAMETER ExternalNetAdapter + Specify a specific network adapter to bind to a DHCP network + + .PARAMETER SkipDefaultHost + Prevents setting localhost as the default network configuration + + .PARAMETER Force + If a restart is required, forces an immediate restart. + + .PARAMETER HyperV + If passed, prepare the machine for Hyper-V containers + + .PARAMETER NATSubnet + Use to override the default Docker NAT Subnet when in NAT mode. + + .PARAMETER NoRestart + If a restart is required the script will terminate and will not reboot the machine + + .PARAMETER ContainerBaseImage + Use this to specify the URI of the container base image you wish to pre-pull + + .PARAMETER Staging + + .PARAMETER TransparentNetwork + If passed, use DHCP configuration. Otherwise, will use default docker network (NAT). (alias -UseDHCP) + + .PARAMETER TarPath + Path to the .tar that is the base image to load into Docker. + + .EXAMPLE + .\install-docker-ce.ps1 + +#> +#Requires -Version 5.0 + +[CmdletBinding(DefaultParameterSetName="Standard")] +param( + [string] + [ValidateNotNullOrEmpty()] + $DockerPath = "default", + + [string] + [ValidateNotNullOrEmpty()] + $DockerDPath = "default", + + [string] + [ValidateNotNullOrEmpty()] + $DockerVersion = "latest", + + [string] + $ExternalNetAdapter, + + [switch] + $Force, + + [switch] + $HyperV, + + [switch] + $SkipDefaultHost, + + [string] + $NATSubnet, + + [switch] + $NoRestart, + + [string] + $ContainerBaseImage, + + [Parameter(ParameterSetName="Staging", Mandatory)] + [switch] + $Staging, + + [switch] + [alias("UseDHCP")] + $TransparentNetwork, + + [string] + [ValidateNotNullOrEmpty()] + $TarPath +) + +$global:RebootRequired = $false +$global:ErrorFile = "$pwd\Install-ContainerHost.err" +$global:BootstrapTask = "ContainerBootstrap" +$global:HyperVImage = "NanoServer" +$global:AdminPriviledges = $false + +$global:DefaultDockerLocation = "https://download.docker.com/win/static/stable/x86_64/" +$global:DockerDataPath = "$($env:ProgramData)\docker" +$global:DockerServiceName = "docker" + +function +Restart-And-Run() +{ + Test-Admin + + Write-Output "Restart is required; restarting now..." + + $argList = $script:MyInvocation.Line.replace($script:MyInvocation.InvocationName, "") + + # + # Update .\ to the invocation directory for the bootstrap + # + $scriptPath = $script:MyInvocation.MyCommand.Path + + $argList = $argList -replace "\.\\", "$pwd\" + + if ((Split-Path -Parent -Path $scriptPath) -ne $pwd) + { + $sourceScriptPath = $scriptPath + $scriptPath = "$pwd\$($script:MyInvocation.MyCommand.Name)" + + Copy-Item $sourceScriptPath $scriptPath + } + + Write-Output "Creating scheduled task action ($scriptPath $argList)..." + $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoExit $scriptPath $argList" + + Write-Output "Creating scheduled task trigger..." + $trigger = New-ScheduledTaskTrigger -AtLogOn + + Write-Output "Registering script to re-run at next user logon..." + Register-ScheduledTask -TaskName $global:BootstrapTask -Action $action -Trigger $trigger -RunLevel Highest | Out-Null + + try + { + if ($Force) + { + Restart-Computer -Force + } + else + { + Restart-Computer + } + } + catch + { + Write-Error $_ + + Write-Output "Please restart your computer manually to continue script execution." + } + + exit +} + + +function +Install-Feature +{ + [CmdletBinding()] + param( + [ValidateNotNullOrEmpty()] + [string] + $FeatureName + ) + + Write-Output "Querying status of Windows feature: $FeatureName..." + if (Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue) + { + if ((Get-WindowsFeature $FeatureName).Installed) + { + Write-Output "Feature $FeatureName is already enabled." + } + else + { + Test-Admin + + Write-Output "Enabling feature $FeatureName..." + } + + $featureInstall = Add-WindowsFeature $FeatureName + + if ($featureInstall.RestartNeeded -eq "Yes") + { + $global:RebootRequired = $true; + } + } + else + { + if ((Get-WindowsOptionalFeature -Online -FeatureName $FeatureName).State -eq "Disabled") + { + if (Test-Nano) + { + throw "This NanoServer deployment does not include $FeatureName. Please add the appropriate package" + } + + Test-Admin + + Write-Output "Enabling feature $FeatureName..." + $feature = Enable-WindowsOptionalFeature -Online -FeatureName $FeatureName -All -NoRestart + + if ($feature.RestartNeeded -eq "True") + { + $global:RebootRequired = $true; + } + } + else + { + Write-Output "Feature $FeatureName is already enabled." + + if (Test-Nano) + { + # + # Get-WindowsEdition is not present on Nano. On Nano, we assume reboot is not needed + # + } + elseif ((Get-WindowsEdition -Online).RestartNeeded) + { + $global:RebootRequired = $true; + } + } + } +} + + +function +New-ContainerTransparentNetwork +{ + # Check if transparent network already created + $networkList = docker network ls + if ($networkList -match '\bTransparent\b') + { + Write-Output "Network with the name Transparent exists." + return + } + + # Continue with network creation + if ($ExternalNetAdapter) + { + $netAdapter = (Get-NetAdapter |? {$_.Name -eq "$ExternalNetAdapter"})[0] + } + else + { + $netAdapter = (Get-NetAdapter |? {($_.Status -eq 'Up') -and ($_.ConnectorPresent)})[0] + } + + Write-Output "Creating container network (Transparent)..." + docker network create -d transparent -o com.docker.network.windowsshim.interface="$($netAdapter.Name)" "Transparent" + if ($LASTEXITCODE -ne 0) { + throw "Failed to create transparent network." + } + + # Transparent networks are not picked up by docker until after a service restart. + if (Test-Docker) + { + Restart-Service -Name $global:DockerServiceName + Wait-Docker + } +} + + +function +Install-ContainerHost +{ + "If this file exists when Install-ContainerHost.ps1 exits, the script failed!" | Out-File -FilePath $global:ErrorFile + + if (Test-Client) + { + if (-not $HyperV) + { + Write-Output "Enabling Hyper-V containers by default for Client SKU" + $HyperV = $true + } + } + # + # Validate required Windows features + # + Install-Feature -FeatureName Containers + + if ($HyperV) + { + Install-Feature -FeatureName Hyper-V + } + + if ($global:RebootRequired) + { + if ($NoRestart) + { + Write-Warning "A reboot is required; stopping script execution" + exit + } + + Restart-And-Run + } + + # + # Unregister the bootstrap task, if it was previously created + # + if ((Get-ScheduledTask -TaskName $global:BootstrapTask -ErrorAction SilentlyContinue) -ne $null) + { + Unregister-ScheduledTask -TaskName $global:BootstrapTask -Confirm:$false + } + + # + # Install, register, and start Docker + # + if (Test-Docker) + { + Write-Output "Docker is already installed." + } + else + { + if ($NATSubnet) + { + Install-Docker -DockerPath $DockerPath -DockerDPath $DockerDPath -NATSubnet $NATSubnet -ContainerBaseImage $ContainerBaseImage + } + else + { + Install-Docker -DockerPath $DockerPath -DockerDPath $DockerDPath -ContainerBaseImage $ContainerBaseImage + } + } + + # + # Configure networking + # + if ($($PSCmdlet.ParameterSetName) -ne "Staging") + { + if ($TransparentNetwork) + { + Write-Output "Waiting for Hyper-V Management..." + $networks = $null + + try + { + $networks = Get-ContainerNetwork -ErrorAction SilentlyContinue + } + catch + { + # + # If we can't query network, we are in bootstrap mode. Assume no networks + # + } + + if ($networks.Count -eq 0) + { + Write-Output "Enabling container networking..." + New-ContainerTransparentNetwork + } + else + { + Write-Output "Networking is already configured. Confirming configuration..." + + $transparentNetwork = $networks |? { $_.Mode -eq "Transparent" } + + if ($transparentNetwork -eq $null) + { + Write-Output "We didn't find a configured external network; configuring now..." + New-ContainerTransparentNetwork + } + else + { + if ($ExternalNetAdapter) + { + $netAdapters = (Get-NetAdapter |? {$_.Name -eq "$ExternalNetAdapter"}) + + if ($netAdapters.Count -eq 0) + { + throw "No adapters found that match the name $ExternalNetAdapter" + } + + $netAdapter = $netAdapters[0] + $transparentNetwork = $networks |? { $_.NetworkAdapterName -eq $netAdapter.InterfaceDescription } + + if ($transparentNetwork -eq $null) + { + throw "One or more external networks are configured, but not on the requested adapter ($ExternalNetAdapter)" + } + + Write-Output "Configured transparent network found: $($transparentNetwork.Name)" + } + else + { + Write-Output "Configured transparent network found: $($transparentNetwork.Name)" + } + } + } + } + } + + if ($TarPath) + { + cmd /c "docker load -i `"$TarPath`"" + } + + Remove-Item $global:ErrorFile + + Write-Output "Script complete!" +} + +function +Copy-File +{ + [CmdletBinding()] + param( + [string] + $SourcePath, + + [string] + $DestinationPath + ) + + if ($SourcePath -eq $DestinationPath) + { + return + } + + if (Test-Path $SourcePath) + { + Copy-Item -Path $SourcePath -Destination $DestinationPath + } + elseif (($SourcePath -as [System.URI]).AbsoluteURI -ne $null) + { + if (Test-Nano) + { + $handler = New-Object System.Net.Http.HttpClientHandler + $client = New-Object System.Net.Http.HttpClient($handler) + $client.Timeout = New-Object System.TimeSpan(0, 30, 0) + $cancelTokenSource = [System.Threading.CancellationTokenSource]::new() + $responseMsg = $client.GetAsync([System.Uri]::new($SourcePath), $cancelTokenSource.Token) + $responseMsg.Wait() + + if (!$responseMsg.IsCanceled) + { + $response = $responseMsg.Result + if ($response.IsSuccessStatusCode) + { + $downloadedFileStream = [System.IO.FileStream]::new($DestinationPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write) + $copyStreamOp = $response.Content.CopyToAsync($downloadedFileStream) + $copyStreamOp.Wait() + $downloadedFileStream.Close() + if ($copyStreamOp.Exception -ne $null) + { + throw $copyStreamOp.Exception + } + } + } + } + elseif ($PSVersionTable.PSVersion.Major -ge 5) + { + # + # We disable progress display because it kills performance for large downloads (at least on 64-bit PowerShell) + # + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $SourcePath -OutFile $DestinationPath -UseBasicParsing + $ProgressPreference = 'Continue' + } + else + { + $webClient = New-Object System.Net.WebClient + $webClient.DownloadFile($SourcePath, $DestinationPath) + } + } + else + { + throw "Cannot copy from $SourcePath" + } +} + + +function +Test-Admin() +{ + # Get the ID and security principal of the current user account + $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent() + $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID) + + # Get the security principal for the Administrator role + $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator + + # Check to see if we are currently running "as Administrator" + if ($myWindowsPrincipal.IsInRole($adminRole)) + { + $global:AdminPriviledges = $true + return + } + else + { + # + # We are not running "as Administrator" + # Exit from the current, unelevated, process + # + throw "You must run this script as administrator" + } +} + + +function +Test-Client() +{ + return (-not ((Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue) -or (Test-Nano))) +} + + +function +Test-Nano() +{ + $EditionId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID').EditionId + + return (($EditionId -eq "ServerStandardNano") -or + ($EditionId -eq "ServerDataCenterNano") -or + ($EditionId -eq "NanoServer") -or + ($EditionId -eq "ServerTuva")) +} + + +function +Wait-Network() +{ + $connectedAdapter = Get-NetAdapter |? ConnectorPresent + + if ($connectedAdapter -eq $null) + { + throw "No connected network" + } + + $startTime = Get-Date + $timeElapsed = $(Get-Date) - $startTime + + while ($($timeElapsed).TotalMinutes -lt 5) + { + $readyNetAdapter = $connectedAdapter |? Status -eq 'Up' + + if ($readyNetAdapter -ne $null) + { + return; + } + + Write-Output "Waiting for network connectivity..." + Start-Sleep -sec 5 + + $timeElapsed = $(Get-Date) - $startTime + } + + throw "Network not connected after 5 minutes" +} + + +function +Install-Docker() +{ + [CmdletBinding()] + param( + [string] + [ValidateNotNullOrEmpty()] + $DockerPath = "default", + + [string] + [ValidateNotNullOrEmpty()] + $DockerDPath = "default", + + [string] + [ValidateNotNullOrEmpty()] + $NATSubnet, + + [switch] + $SkipDefaultHost, + + [string] + $ContainerBaseImage + ) + + Test-Admin + + #If one of these are set to default then the whole .zip needs to be downloaded anyways. + Write-Output "DOCKER $DockerPath" + if ($DockerPath -eq "default" -or $DockerDPath -eq "default") { + Write-Output "Checking Docker versions" + #Get the list of .zip packages available from docker. + $availableVersions = ((Invoke-WebRequest -Uri $DefaultDockerLocation -UseBasicParsing).Links | Where-Object {$_.href -like "docker*"}).href | Sort-Object -Descending + + #Parse the versions from the file names + $availableVersions = ($availableVersions | Select-String -Pattern "docker-(\d+\.\d+\.\d+).+" -AllMatches | Select-Object -Expand Matches | %{ $_.Groups[1].Value }) + $version = $availableVersions[0] + + if($DockerVersion -ne "latest") { + $version = $DockerVersion + if(!($availableVersions | Select-String $DockerVersion)) { + Write-Error "Docker version supplied $DockerVersion was invalid, please choose from the list of available versions: $availableVersions" + throw "Invalid docker version supplied." + } + } + + $zipUrl = $global:DefaultDockerLocation + "docker-$version.zip" + $destinationFolder = "$env:UserProfile\DockerDownloads" + + if(!(Test-Path "$destinationFolder")) { + md -Path $destinationFolder | Out-Null + } elseif(Test-Path "$destinationFolder\docker-$version") { + Remove-Item -Recurse -Force "$destinationFolder\docker-$version" + } + + Write-Output "Downloading $zipUrl to $destinationFolder\docker-$version.zip" + Copy-File -SourcePath $zipUrl -DestinationPath "$destinationFolder\docker-$version.zip" + + #Prevent issues with CLI non-interactive execution on Window Server 2019 + $global:ProgressPreference = "SilentlyContinue" + Expand-Archive -Path "$destinationFolder\docker-$version.zip" -DestinationPath "$destinationFolder\docker-$version" + $global:ProgressPreference = "Continue" + + if($DockerPath -eq "default") { + $DockerPath = "$destinationFolder\docker-$version\docker\docker.exe" + } + if($DockerDPath -eq "default") { + $DockerDPath = "$destinationFolder\docker-$version\docker\dockerd.exe" + } + } + + Write-Output "Installing Docker... $DockerPath" + Copy-File -SourcePath $DockerPath -DestinationPath $env:windir\System32\docker.exe + + Write-Output "Installing Docker daemon... $DockerDPath" + Copy-File -SourcePath $DockerDPath -DestinationPath $env:windir\System32\dockerd.exe + + $dockerConfigPath = Join-Path $global:DockerDataPath "config" + + if (!(Test-Path $dockerConfigPath)) + { + md -Path $dockerConfigPath | Out-Null + } + + # + # Register the docker service. + # Configuration options should be placed at %programdata%\docker\config\daemon.json + # + Write-Output "Configuring the docker service..." + + $daemonSettings = New-Object PSObject + + $certsPath = Join-Path $global:DockerDataPath "certs.d" + + if (Test-Path $certsPath) + { + $daemonSettings | Add-Member NoteProperty hosts @("npipe://", "0.0.0.0:2376") + $daemonSettings | Add-Member NoteProperty tlsverify true + $daemonSettings | Add-Member NoteProperty tlscacert (Join-Path $certsPath "ca.pem") + $daemonSettings | Add-Member NoteProperty tlscert (Join-Path $certsPath "server-cert.pem") + $daemonSettings | Add-Member NoteProperty tlskey (Join-Path $certsPath "server-key.pem") + } + elseif (!$SkipDefaultHost.IsPresent) + { + # Default local host + $daemonSettings | Add-Member NoteProperty hosts @("npipe://") + } + + if ($NATSubnet -ne "") + { + $daemonSettings | Add-Member NoteProperty fixed-cidr $NATSubnet + } + + $daemonSettingsFile = Join-Path $dockerConfigPath "daemon.json" + + $daemonSettings | ConvertTo-Json | Out-File -FilePath $daemonSettingsFile -Encoding ASCII + + & dockerd --register-service --service-name $global:DockerServiceName + + Start-Docker + + # + # Waiting for docker to come to steady state + # + Wait-Docker + + if(-not [string]::IsNullOrEmpty($ContainerBaseImage)) { + Write-Output "Attempting to pull specified base image: $ContainerBaseImage" + docker pull $ContainerBaseImage + } + + Write-Output "The following images are present on this machine:" + + docker images -a | Write-Output + + Write-Output "" +} + +function +Start-Docker() +{ + Start-Service -Name $global:DockerServiceName +} + + +function +Stop-Docker() +{ + Stop-Service -Name $global:DockerServiceName +} + + +function +Test-Docker() +{ + $service = Get-Service -Name $global:DockerServiceName -ErrorAction SilentlyContinue + + return ($service -ne $null) +} + + +function +Wait-Docker() +{ + Write-Output "Waiting for Docker daemon..." + $dockerReady = $false + $startTime = Get-Date + + while (-not $dockerReady) + { + try + { + docker version | Out-Null + + if (-not $?) + { + throw "Docker daemon is not running yet" + } + + $dockerReady = $true + } + catch + { + $timeElapsed = $(Get-Date) - $startTime + + if ($($timeElapsed).TotalMinutes -ge 1) + { + throw "Docker Daemon did not start successfully within 1 minute." + } + + # Swallow error and try again + Start-Sleep -sec 1 + } + } + Write-Output "Successfully connected to Docker Daemon." +} + +try +{ + Install-ContainerHost +} +catch +{ + Write-Error $_ +} \ No newline at end of file