grafana / loki

Like Prometheus, but for logs.
https://grafana.com/loki
GNU Affero General Public License v3.0
23.92k stars 3.45k forks source link

Docs: installing Promtail on Windows isn't well documented #9392

Open jpkrohling opened 1 year ago

jpkrohling commented 1 year ago

I wanted to share feedback I heard from the community on the Grafana Brazil Telegram channel (https://t.me/grafanabr): installing Promtail on a Windows host needs to be better documented and more user-friendly. Users expect an installer to exist, installing both service configuration and the binary. Without that, users have to figure out how to place the binary under a service by themselves. A temporary remedy is to provide official instructions on how to create those services. I have not tested this myself, as I don't have a Windows host, but here's a set of commands that would work for this:

New-Item -Path "c:\" -Name "promtail" -ItemType "directory"

$promtail_url = "https://github.com/grafana/loki/releases/download/v2.8.1/promtail-windows-amd64.exe.zip"
$windows_dir = "c:\promtail\"

$client = New-Object System.Net.WebClient
$client.DownloadFile($promtail_url, $windows_dir)

Expand-Archive -LiteralPath "c:\promtail\promtail-windows-amd64.exe.zip" -DestinationPath "c:\promtail"

# Before running the next command, copy the config to "c:\promtail\promtail-local-config.yaml"

New-Service -Name "Promtail" -DisplayName = "Monitoracao de Logs" -StartupType = "Automatic" -BinaryPathName "C:\promtail\\promtail-windows-amd64.exe --config.file=promtail-local-config.yaml"
myysophia commented 1 year ago

+1

prastamaha commented 1 year ago

I tried with the New-service command you provided above and it failed. I searched and found http://nssm.cc/, I tried running with this and it worked.

JavierCCC commented 1 year ago

It doesnt work. The only option that seems to work is using NSSM... can't believe this. I've opened a new question about this topic. If promtail binary can't run by itself as a windows service, how should i run? manually?

https://github.com/grafana/loki/issues/10207

riechst1 commented 1 year ago

I wanted to share feedback I heard from the community on the Grafana Brazil Telegram channel (https://t.me/grafanabr): installing Promtail on a Windows host needs to be better documented and more user-friendly. Users expect an installer to exist, installing both service configuration and the binary. Without that, users have to figure out how to place the binary under a service by themselves. A temporary remedy is to provide official instructions on how to create those services. I have not tested this myself, as I don't have a Windows host, but here's a set of commands that would work for this:

New-Item -Path "c:\" -Name "promtail" -ItemType "directory"

$promtail_url = "https://github.com/grafana/loki/releases/download/v2.8.1/promtail-windows-amd64.exe.zip"
$windows_dir = "c:\promtail\"

$client = New-Object System.Net.WebClient
$client.DownloadFile($promtail_url, $windows_dir)

Expand-Archive -LiteralPath "c:\promtail\promtail-windows-amd64.exe.zip" -DestinationPath "c:\promtail"

# Before running the next command, copy the config to "c:\promtail\promtail-local-config.yaml"

New-Service -Name "Promtail" -DisplayName = "Monitoracao de Logs" -StartupType = "Automatic" -BinaryPathName "C:\promtail\\promtail-windows-amd64.exe --config.file=promtail-local-config.yaml"

This suggestion works in creating the service. However, when trying to start the service it fails. In the windows event I see error message about reaching a timeout and the service was unresponsive. When using NSSM to create the service it creates the service that the system can actually start and stop. For a dev environment I don't mind having a wrapper since we are still evaluating this agent. But the lack of documentation to setup the agent in a windows server makes it less attractive. I hope someone can post an actual config/steps on how to setup the agent as a service in windows.

FlanniganColum commented 11 months ago

I am also facing the same issue while trying to evaluate loki as a solution. has anyone found a way around this??

pompushko commented 9 months ago

+1

joseparedespy commented 8 months ago

news?

JStickler commented 8 months ago

Part of the reason why I added the help wanted flag to this issue is that I don't think we have anyone on the team who is running Windows. This information is going to have to be added by someone more familiar with Windows.

aehndi commented 8 months ago

I have not tried with promtail till now, but am currently evaluating some exporters for windows that I run as scheduled tasks (found this on evaluating the windows_exporter.it remembering exactly where). I am running this approach since a few days on a test system, not sure if its a stable option. At least its restarting on reboot. May be its worth a try... I can provide a PS script for creating the task when i am back at work, if someone s in need

OndrejValenta commented 8 months ago

UPDATED VERSION based on feedback.

I've prepared a script for Promtail Windows Service installation, with download progress bar, ensure of run folder, default config and everything.. Give it a try.

Install-PromtailOnWindows.ps1

#############################################################################################
# This script downloads, if necessary, Promtail and its WinSW (Windows Service wrapper), 
# creates default configuration and creates Windows service.
# It's a decision based script.
# 
# ↓ ↓ ↓ ↓       HELPER FUNCTIONS    ↓ ↓ ↓ ↓
# ↓↓↓↓↓↓↓       PROCESSING CODE     ↓↓↓↓↓↓↓
#############################################################################################

function Prompt-User {
    param(
        [string]$Prompt,
        [object]$Default
    )

    if (-not [string]::IsNullOrEmpty($Default)) {
        if ($Default -is [bool]) {
            $Prompt += " [$(if ($Default) {'True'} else {'False'})]"
        }
        else {
            $Prompt += " [$Default]"
        }
    }

    $input = Read-Host -Prompt $Prompt

    if ([string]::IsNullOrEmpty($input)) {
        $input = $Default
    }
    else {
        if ($Default -is [bool]) {
            $input = [bool]::Parse($input)
        }
        elseif ($Default -is [int]) {
            $input = [int]::Parse($input)
        }
        elseif ($Default -is [string]) {
            # No conversion needed for string
        }
        else {
            throw "Unsupported default value type: $($Default.GetType().FullName)"
        }

        if ($input.GetType() -ne $Default.GetType()) {
            throw "Entered value type doesn't match default value type"
        }
    }

    return $input
}

function Ensure-Directory {
    param(
        [string]$Path
    )

    if (-not [System.IO.Directory]::Exists($Path)) {
        [System.IO.Directory]::CreateDirectory($Path) | Out-Null
    }
}

function Download-File {
    param (
        # Url of file to download
        [Parameter(Mandatory)]
        [string]$URL,

        # Parameter help description
        [Parameter(Mandatory)]
        [string]$File 
    )
    Begin {
        function Show-Progress {
            param (
                # Enter total value
                [Parameter(Mandatory)]
                [Single]$TotalValue,

                # Enter current value
                [Parameter(Mandatory)]
                [Single]$CurrentValue,

                # Enter custom progresstext
                [Parameter(Mandatory)]
                [string]$ProgressText,

                # Enter value suffix
                [Parameter()]
                [string]$ValueSuffix,

                # Enter bar lengh suffix
                [Parameter()]
                [int]$BarSize = 40,

                # show complete bar
                [Parameter()]
                [switch]$Complete
            )

            # calc %
            $percent = $CurrentValue / $TotalValue
            $percentComplete = $percent * 100
            if ($ValueSuffix) {
                $ValueSuffix = " $ValueSuffix" # add space in front
            }
            if ($psISE) {
                Write-Progress "$ProgressText $CurrentValue$ValueSuffix of $TotalValue$ValueSuffix" -id 0 -percentComplete $percentComplete            
            }
            else {
                # build progressbar with string function
                $curBarSize = $BarSize * $percent
                $progbar = ""
                $progbar = $progbar.PadRight($curBarSize, [char]9608)
                $progbar = $progbar.PadRight($BarSize, [char]9617)

                if (!$Complete.IsPresent) {
                    Write-Host -NoNewLine "`r$ProgressText $progbar [ $($CurrentValue.ToString("#.###").PadLeft($TotalValue.ToString("#.###").Length))$ValueSuffix / $($TotalValue.ToString("#.###"))$ValueSuffix ] $($percentComplete.ToString("##0.00").PadLeft(6)) % complete"
                }
                else {
                    Write-Host -NoNewLine "`r$ProgressText $progbar [ $($TotalValue.ToString("#.###").PadLeft($TotalValue.ToString("#.###").Length))$ValueSuffix / $($TotalValue.ToString("#.###"))$ValueSuffix ] $($percentComplete.ToString("##0.00").PadLeft(6)) % complete"                    
                }                
            }   
        }
    }
    Process {
        try {
            $storeEAP = $ErrorActionPreference
            $ErrorActionPreference = 'Stop'

            # invoke request
            $request = [System.Net.HttpWebRequest]::Create($URL)
            $response = $request.GetResponse()

            if ($response.StatusCode -eq 401 -or $response.StatusCode -eq 403 -or $response.StatusCode -eq 404) {
                throw "Remote file either doesn't exist, is unauthorized, or is forbidden for '$URL'."
            }

            if ($File -match '^\.\\') {
                $File = Join-Path (Get-Location -PSProvider "FileSystem") ($File -Split '^\.')[1]
            }

            if ($File -and !(Split-Path $File)) {
                $File = Join-Path (Get-Location -PSProvider "FileSystem") $File
            }

            if ($File) {
                $fileDirectory = $([System.IO.Path]::GetDirectoryName($File))
                if (!(Test-Path($fileDirectory))) {
                    [System.IO.Directory]::CreateDirectory($fileDirectory) | Out-Null
                }
            }

            [long]$fullSize = $response.ContentLength
            $fullSizeMB = $fullSize / 1024 / 1024

            # define buffer
            [byte[]]$buffer = new-object byte[] 1048576
            [long]$total = [long]$count = 0

            # create reader / writer
            $reader = $response.GetResponseStream()
            $writer = new-object System.IO.FileStream $File, "Create"

            # start download
            $finalBarCount = 0 #show final bar only one time
            do {

                $count = $reader.Read($buffer, 0, $buffer.Length)

                $writer.Write($buffer, 0, $count)

                $total += $count
                $totalMB = $total / 1024 / 1024

                if ($fullSize -gt 0) {
                    Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix "MB"
                }

                if ($total -eq $fullSize -and $count -eq 0 -and $finalBarCount -eq 0) {
                    Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix "MB" -Complete
                    $finalBarCount++
                    #Write-Host "$finalBarCount"
                }

            } while ($count -gt 0)
        }

        catch {

            $ExeptionMsg = $_.Exception.Message
            Write-Host "Download breaks with error : $ExeptionMsg"
        }

        finally {
            # cleanup
            if ($reader) { $reader.Close() }
            if ($writer) { $writer.Flush(); $writer.Close() }

            $ErrorActionPreference = $storeEAP
            [GC]::Collect()

            Write-Host 
        }    
    }
}

function New-DefaultConfig {
    param (
        # Parameter help description
        [Parameter(Mandatory)]
        [string]$fullConfigPath,
        [Parameter(Mandatory)]
        [string]$runPath
    )

    Write-Output "Writing default config to $fullConfigPath"

    $positionsFullpath = $runPath + "\positions.yaml"

    $content = "
# 1. Update positions.yaml path
# 2. Update client's url - this is the url of Loki service - update or remove basic_auth
# 3. Update what logs should be scraped

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: $positionsFullpath

clients:
- url: https://[loki-url]:3100/loki/api/v1/push
  basic_auth:
    username: [loki]
    password: [loki password]
scrape_configs:
- job_name: winlogs
  static_configs:
  - targets:
      - localhost
    labels:
      host: [hostname]
      job: [winlogs]
      #NOTE: Need to be modified to scrape any additional logs of the system.
      __path__: C:\Logs\*.log
"

    Set-Content -Path $fullConfigPath -Value $content
}

function New-DefaultWinSWConfig {
    param (
        # Parameter help description
        [Parameter(Mandatory)]
        [string]$fullConfigPath,
        [Parameter(Mandatory)]
        [string]$fullPromtailBinPath,
        [Parameter(Mandatory)]
        [string]$fullPromtailConfigPath,
        [Parameter(Mandatory)]
        [string]$serviceName,
        [Parameter(Mandatory)]
        [string]$serviceDisplayName
    )

    Write-Output "Writing default WinSW config to $fullConfigPath"

    $content = "
    <!--
    You can find more information about the configuration options here: https://github.com/kohsuke/winsw/blob/master/doc/xmlConfigFile.md
    Full example: https://github.com/kohsuke/winsw/blob/master/examples/sample-allOptions.xml
   -->
   <service>

     <!-- ID of the service. It should be unique across the Windows system-->
     <id>$serviceName</id>
     <!-- Display name of the service -->
     <name>$serviceDisplayName</name>
     <!-- Service description -->
     <description>Starts a local Promtail service and scrapes logs according to configuration file: $fullPromtailConfigPath</description>

     <!-- Path to the executable, which should be started -->
     <executable>""$fullPromtailBinPath""</executable>

     <arguments>--config.file=""$fullPromtailConfigPath""</arguments>

   </service>
"

    Set-Content -Path $fullConfigPath -Value $content
}

#############################################
# ↑ ↑ ↑ ↑   HELPER FUNCTIONS          ↑ ↑ ↑ ↑   
# 
#
# ↓ ↓ ↓ ↓   PROCESSING CODE           ↓ ↓ ↓ ↓
#############################################

Write-Warning -Message "This script creates a Window Service for Promtail log scraper. It is necessary to run it with Admin priviledges.

It can download necessary files from the Internet, but you can also put already downloaded files directly to proper directories."

$downloadUrl = "https://github.com/grafana/loki/releases/download/v2.9.5/promtail-windows-amd64.exe.zip"
$downloadWinSWUrl = "https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe"
$winSWFilename = "WinSW-x64.exe"
$binFilename = "promtail-windows-amd64.exe"
$configFilename = "promtail.yml"
$winSWConfigFilename = "WinSW-x64.xml"

$runPath = Prompt-User -Prompt "Run directory" -Default "C:\Promtail"
$fullBinPath = Join-Path -Path $runPath -ChildPath $binFilename
$fullWinSWBinPath = Join-Path -Path $runPath -ChildPath $winSWFilename
$downloadWinSWPath = $runPath

$shouldDownloadPromtail = Prompt-User -Prompt "Should we download Promtail?" -Default $true

if ($shouldDownloadPromtail) {
    $downloadUrl = Prompt-User -Prompt "Download url" -Default $downloadUrl
    $downloadPath = Prompt-User -Prompt "Download directory" -Default $runPath

    Ensure-Directory -Path $downloadPath

    $filename = $downloadUrl.Split("/")[-1]
    $fullPath = Join-Path -Path $downloadPath -ChildPath $filename

    Write-Host "Downloading archive..."
    Download-File -URL $downloadUrl -File $fullPath

    Write-Host "Expanding archive..."
    Expand-Archive -LiteralPath $fullPath -DestinationPath $runPath -Force
} 
else {
    if (-not [System.IO.File]::Exists($fullBinPath)) {
        throw "Could not find $fullBinPath"
    }
}

$shouldCreateConfig = Prompt-User -Prompt "Create default promtail.yml config?" -Default $true
$configFullpath = Join-Path -Path $runPath -ChildPath $configFilename

if ($shouldCreateConfig) {
    New-DefaultConfig -fullConfigPath $configFullpath -runPath $runPath
}
else {
    $configFullpath = Prompt-User -Prompt "Promtail configuration file path" -Default $configFullpath

    if (-not [System.IO.File]::Exists($configFullpath)) {
        throw "Could not find $configFullpath"
    }
}

$shouldCreateService = Prompt-User -Prompt "Create Promtail Windows service?" -Default $false

if ($shouldCreateService) {

    $shouldDownloadWinSWUrl = Prompt-User -Prompt "Should we download Windows Service wrapper (WinSWUrl)?" -Default $true

    if ($shouldDownloadWinSWUrl) {
        $downloadUrl = Prompt-User -Prompt "Download url" -Default $downloadWinSWUrl
        $downloadWinSWPath = Prompt-User -Prompt "Download directory" -Default $runPath

        Ensure-Directory -Path $downloadPath

        $filename = $winSWFilename
        $fullWinSWBinPath = Join-Path -Path $downloadWinSWPath -ChildPath $filename

        Write-Host "Downloading WinSW exe file..."
        Download-File -URL $downloadUrl -File $fullWinSWBinPath
    } 
    else {
        if (-not [System.IO.File]::Exists($fullWinSWBinPath)) {
            throw "Could not find $fullWinSWBinPath"
        }
    }

    $winSWconfigFullpath = Join-Path -Path $downloadWinSWPath -ChildPath $winSWConfigFilename
    $shouldCreateWinSWConfig = Prompt-User -Prompt "Create WinSW config as $winSWconfigFullpath ?" -Default $true

    if ($shouldCreateWinSWConfig) {
        $serviceName = Prompt-User -Prompt "Service name" -Default "Promtail"
        $serviceDisplayName = Prompt-User -Prompt "Service name" -Default "Promtail Logs scraper"

        New-DefaultWinSWConfig -fullConfigPath $winSWconfigFullpath -fullPromtailBinPath $fullBinPath -fullPromtailConfigPath $configFullpath -serviceName $serviceName -serviceDisplayName $serviceDisplayName
    }
    else {
        if (-not [System.IO.File]::Exists($winSWconfigFullpath)) {
            throw "Could not find $winSWconfigFullpath"
        }
    }

    Write-Host "Installing Promtail Windows Service"

    Start-Process -FilePath $fullWinSWBinPath -ArgumentList @("install") -NoNewWindow

    Write-Host "Promtail Windows Service Installed (hopefully)"
}

WinSW-x64.xml file now has paths in quotes just to be sure you don't run into issues when using spaces in paths.

image

Btw. to remove the service, you could use Remove-Service but that is available only from PowerShell 6 and above. If you have it you can run

Remove-Service -Name Promtail

https://github.com/grafana/loki/assets/6738956/13789456-69db-4449-8b84-70b6b8cf4302

headlessnetsman commented 7 months ago

@OndrejValenta Nice script! I didn't run it but I did try the following code, and with no avail, the service still will not start:

$params = @{
    Name = "Promtail"
    BinaryPathName = "C:\Promtail\promtail-windows-amd64.exe --config.file=C:\Promtail\promtail-config.yml"
    DisplayName = "Promtail"
    StartupType = "Automatic"
    Description = "Starts a local Promtail service and scrapes logs"
}

New-Service @params
OndrejValenta commented 7 months ago

@OndrejValenta Nice script! I didn't run it but I did try the following code, and with no avail, the service still will not start:

$params = @{
    Name = "Promtail"
    BinaryPathName = "C:\Promtail\promtail-windows-amd64.exe --config.file=C:\Promtail\promtail-config.yml"
    DisplayName = "Promtail"
    StartupType = "Automatic"
    Description = "Starts a local Promtail service and scrapes logs"
}

New-Service @params

Yes, I have to rework it. As it turns out, promtail is not ready to be a Windows Service on it's own and has to be wrapped with Windows Service Wrapper

This is an example of v2 configuration file named WinSW-x64.xml (as it must be called in WinSW v2), which is sitting right next to WinSW-x64.exe.

When you run it, it will create log files so you can see promtail output.

To install the service WinSW-x64.exe install in the folder where xml/exe files are

<service>

  <!-- ID of the service. It should be unique across the Windows system-->
  <id>Promtail</id>
  <!-- Display name of the service -->
  <name>Promtail Logs scraper</name>
  <!-- Service description -->
  <description>Starts a local Promtail service and scrapes logs</description>

  <!-- Path to the executable, which should be started -->
  <executable>H:\Programs\Promtail\promtail-windows-amd64.exe</executable>
  <arguments>--config.file=.\promtail.yml</arguments>

</service>
headlessnetsman commented 7 months ago

Yes, I have to rework it. As it turns out, promtail is not ready to be a Windows Service on it's own and has to be wrapped with Windows Service Wrapper

This is an example of v2 configuration file named WinSW-x64.xml (as it must be called in WinSW v2), which is sitting right next to WinSW-x64.exe.

When you run it, it will create log files so you can see promtail output.

To install the service WinSW-x64.exe install in the folder where xml/exe files are

<service>
  <!-- ID of the service. It should be unique across the Windows system-->
  <id>Promtail</id>
  <!-- Display name of the service -->
  <name>Promtail Logs scraper</name>
  <!-- Service description -->
  <description>Starts a local Promtail service and scrapes logs</description>
  <!-- Path to the executable, which should be started -->
  <executable>H:\Programs\Promtail\promtail-windows-amd64.exe</executable>
  <arguments>--config.file=.\promtail.yml</arguments>
</service>

Thank you for acknowledging and providing an alternate solution. Your input is greatly appreciated!

SveDec commented 7 months ago

Yes, I have to rework it. As it turns out, promtail is not ready to be a Windows Service on it's own and has to be wrapped with Windows Service Wrapper This is an example of v2 configuration file named WinSW-x64.xml (as it must be called in WinSW v2), which is sitting right next to WinSW-x64.exe. When you run it, it will create log files so you can see promtail output. To install the service WinSW-x64.exe install in the folder where xml/exe files are

<service>
  <!-- ID of the service. It should be unique across the Windows system-->
  <id>Promtail</id>
  <!-- Display name of the service -->
  <name>Promtail Logs scraper</name>
  <!-- Service description -->
  <description>Starts a local Promtail service and scrapes logs</description>
  <!-- Path to the executable, which should be started -->
  <executable>H:\Programs\Promtail\promtail-windows-amd64.exe</executable>
  <arguments>--config.file=.\promtail.yml</arguments>
</service>

Thank you for acknowledging and providing an alternate solution. Your input is greatly appreciated!

Hi,

First, thanks to all the contributors of this topic !

To complete the answers above, for the WinSW config, note that if a path contains spaces, you have to double quote it ; you also may want to add a log parameter (cf WinSW doc). That gives the following example config :

<service>

  <!-- ID of the service. It should be unique across the Windows system -->
  <id>promtail_agent</id>
  <!-- Display name of the service -->
  <name>Promtail Agent</name>
  <!-- Service description -->
  <description>Log centralization with Promtail Agent</description>

  <!-- Path to the executable, which should be started -->
  <executable>"C:\Program Files\Promtail\promtail-windows-amd64.exe"</executable>
  <arguments>--config.file="C:\Program Files\Promtail\promtail-config.yml"</arguments>

  <!-- Defines logging mode for logs produced by the executable -->
  <log mode="roll"></log>

</service>

Concerning the Promtail config, note that if you want to scrape more than one Windows event logs channel, a easy way to do it is to define a job for each channel (cf https://github.com/grafana/loki/issues/5905#issuecomment-1099352713).

OndrejValenta commented 7 months ago

To complete the answers above, for the WinSW config, note that if a path contains spaces, you have to double quote it ; you also may want to add a log parameter (cf WinSW doc). That gives the following example config :

I have updated the script to do all work necessary. Downloads WinSW wrapper, Promtail, and so on. Also, default WinSW XML contains paths in quotes.

nicolassivan commented 5 months ago

Thank you very much @OndrejValenta ! It works as a charm. I don't know if it was a mistake of my own but I had to rewrite the URL https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe to download winsw when asked by the script. Otherwise, nothing else to share. You saved me several hours of work !

OndrejValenta commented 5 months ago

Thank you very much @OndrejValenta ! It works as a charm. I don't know if it was a mistake of my own but I had to rewrite the URL https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe to download winsw when asked by the script. Otherwise, nothing else to share. You saved me several hours of work !

Interesting, I've just tried https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe and it works just fine. They are going to release v3 soon anyway and the script will have to be updated accordingly. :-)

Enjoy.