Řešení problému s Docker orphan layers na Windows Server 2019


Úvod

Při práci s Dockerem na Windows Server 2019 se můžete setkat s problémem, kdy nevyužívané vrstvy kontejnerů zůstávají na disku a způsobují jeho plnění. Tento článek poskytuje řešení tohoto problému pomocí PowerShell skriptů.

Identifikace Problému

Problém se obvykle projevuje rychlým snižováním volného místa na disku. To je způsobeno tím, že Docker neodstraňuje vrstvy, které již nejsou využívány žádnými kontejnery nebo obrazy.

Úprava daemon.json

Doporučuje se upravit C:\ProgramData\docker\config\daemon.json a to konkrétně přidat "shutdown-timeout": 900, což prodlouží dobu, kterou má Docker na ukončení – a tím i promazání orphan layers.

C:\ProgramData\docker\config\daemon.json
{
    "shutdown-timeout": 900
}

Použité Skripty

Pro řešení tohoto problému byly použity dva PowerShell skripty:

1. Skript pro identifikaci a přejmenování orphan layers

První skript prochází existující Docker image a kontejnery a shromažďuje informace o jejich vrstvách. Poté porovnává tyto vrstvy s vrstvami uloženými na disku a identifikuje ty, které nejsou spojeny s žádným známým imagem nebo kontejnerem. Tyto osiřelé vrstvy mohou být poté přejmenovány pro snadnější odstranění.

param (
    [switch]$RenameOrphanLayers
)
 
If ($RenameOrphanLayers) {
    Write-Warning "$($env:COMPUTERNAME) -RenameOrphanLayers option enabled, will rename all orphan layers"
}
 
# Get known layers on Docker images
[array]$ImageDetails += docker images -q | ForEach { docker inspect $_ | ConvertFrom-Json }
ForEach ($Image in $ImageDetails) {
    $ImageLayer = $Image.GraphDriver.Data.dir
     
    [array]$ImageLayers += $ImageLayer
    $LayerChain = Get-Content "$ImageLayer\layerchain.json"
    If ($LayerChainFileContent -ne "null") {
        [array]$ImageParentLayers += $LayerChain | ConvertFrom-Json
    }
}
 
# Get known layes on Docker containers
[array]$ContainerDetails = docker ps -a -q | ForEach { docker inspect $_ | ConvertFrom-Json}
ForEach ($Container in $ContainerDetails) {
    [array]$ContainerLayers += $Container.GraphDriver.Data.dir
}
 
# Get layers on disk
$LayersOnDisk = (Get-ChildItem -Path C:\ProgramData\Docker\windowsfilter -Directory).FullName
$ImageLayers += $ImageParentLayers
$UniqueImageLayers = $ImageLayers | Select-Object -Unique
[array]$KnownLayers = $UniqueImageLayers
$KnownLayers += $ContainerLayers
 
# Find orphan layers
$OrphanLayersTotal = 0
ForEach ($Layer in $LayersOnDisk) {
    If ($KnownLayers -notcontains $Layer) {
        [array]$OrphanLayer += $Layer
        $LayerSize = (Get-ChildItem -Path $Layer -Recurse -ErrorAction:SilentlyContinue | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum
        $OrphanLayersTotal += $LayerSize
        Write-Warning "$($env:COMPUTERNAME) - Found orphan layer: $($Layer -Replace '\r\n','') with size: $(($LayerSize -Replace '\r\n','') / 1MB) MB"
         
        If (($RenameOrphanLayers) -and ($Layer -notlike "*-removing")) {
            $LayerNewPath = $Layer + "-removing"
            Rename-Item -Path $Layer -NewName $LayerNewPath
        }
    }
}
 
Write-Host "$($env:COMPUTERNAME) - Layers on disk: $($LayersOnDisk.count)"
Write-Host "$($env:COMPUTERNAME) - Image layers: $($UniqueImageLayers.count)"
Write-Host "$($env:COMPUTERNAME) - Container layers: $($ContainerLayers.count)"
$OrphanLayersTotalMB = $OrphanLayersTotal / 1MB
Write-Warning "$($env:COMPUTERNAME) - Found $($OrphanLayer.count) orphan layers 
with total size $OrphanLayersTotalMB MB"

2. Restartovací skript Dockeru

Druhý skript zastavuje všechny služby a procesy Dockeru a poté je restartuje. Tento krok je nezbytný pro odstranění orphan layers, které byly identifikovány a přejmenovány prvním skriptem.

Write-Output "$((Get-Date).ToString("HH:mm:ss")) - Restarting docker"
 
foreach($svc in (Get-Service | Where-Object {$_.name -ilike "*docker*" -and $_.Status -ieq "Running"}))
{
    $svc | Stop-Service -ErrorAction Continue -Confirm:$false -Force
    $svc.WaitForStatus('Stopped','00:00:20')
}
 
Get-Process | Where-Object {$_.Name -ilike "*docker*"} | Stop-Process -ErrorAction Continue -Confirm:$false -Force
 
foreach($svc in (Get-Service | Where-Object {$_.name -ilike "*docker*" -and $_.Status -ieq "Stopped"} ))
{
    $svc | Start-Service
    $svc.WaitForStatus('Running','00:00:20')
}
 
Write-Output "$((Get-Date).ToString("HH:mm:ss")) - Starting Docker"
& "C:\Program Files\Docker\docker.exe"
$startTimeout = [DateTime]::Now.AddSeconds(90)
$timeoutHit = $true
while ((Get-Date) -le $startTimeout)
{
 
    Start-Sleep -Seconds 10
    $ErrorActionPreference = 'Continue'
    try
    {
        $info = (docker info)
        Write-Verbose "$((Get-Date).ToString("HH:mm:ss")) - `tDocker info executed. Is Error?: $($info -ilike "*error*"). Result was: $info"
 
        if ($info -ilike "*error*")
        {
            Write-Verbose "$((Get-Date).ToString("HH:mm:ss")) - `tDocker info had an error. throwing..."
            throw "Error running info command $info"
        }
        $timeoutHit = $false
        break
    }
    catch 
    {
 
        if (($_ -ilike "*error during connect*") -or ($_ -ilike "*errors pretty printing info*")  -or ($_ -ilike "*Error running info command*"))
        {
            Write-Output "$((Get-Date).ToString("HH:mm:ss")) -`t Docker startup not yet completed, waiting and checking again"
        }
        else
        {
            Write-Output "Unexpected Error: `n $_"
            return
        }
    }
    $ErrorActionPreference = 'Stop'
}
if ($timeoutHit -eq $true)
{
    throw "Timeout hit waiting for docker to startup"
}
 
Write-Output "$((Get-Date).ToString("HH:mm:ss")) - Docker restarted"

    Implementace Skriptů

    Bezpečnostní Opatření

    • Před spuštěním skriptů je důležité provést zálohu systému.
    • Doporučuje se nejprve otestovat skripty v kontrolovaném prostředí.
    • Po restartu Dockeru monitorujte systém, abyste ověřili jeho správnou funkčnost.

    Automatizace a Monitoring

    • Skripty mohou být spuštěny manuálně nebo automatizovány pomocí naplánovaných úloh.
    • Doporučuje se implementovat monitoring využití diskového prostoru pro pravidelné sledování stavu systému.

    Závěr

    Použitím těchto PowerShell skriptů můžete efektivně řešit problém s osiřelými vrstvami Dockeru na Windows Server 2019, což vede k uvolnění diskového prostoru a zlepšení výkonu systému.


    Zdroje:

    Leave a Reply