Munkavégzés a Powershell MS SQL-jével Linuxon

Ez a cikk pusztán gyakorlati jellegű, és az én szomorú történetemnek szenteltem

Felkészülés Zero Touch PROD RDS-hez (MS SQL), amitől minden fülünk zúgott, készítettem egy bemutatót (POC - Proof Of Concept) az automatizálásról: powershell szkriptek halmaza. A prezentáció után, amikor a viharos, hosszan tartó taps elült, és szüntelen tapsba csapott át, azt mondták – ez mind jó, de csak ideológiai okokból, minden Jenkins rabszolgánk Linuxon dolgozik!

Lehetséges ez? Vegyünk egy ilyen meleg, lámpás DBA-t a Windows alól, és ragasszuk bele a powershell melegébe Linux alatt? Hát nem kegyetlen ez?

Munkavégzés a Powershell MS SQL-jével Linuxon
El kellett merülnöm a technológiák eme furcsa kombinációjában. Természetesen az összes 30+ szkriptem leállt. Meglepetésemre egy munkanap alatt sikerült mindent rendbe hoznom. Hűvös nyomon írok. Tehát milyen buktatókkal találkozhat, amikor powershell-szkripteket visz át Windowsról Linuxra?

sqlcmd vs Invoke-SqlCmd

Hadd emlékeztesselek a köztük lévő fő különbségre. Jó régi segédprogram sqlcmd Linux alatt is működik, szinte azonos funkcionalitással. A lekérdezést a -Q végrehajtására adjuk át, a bemeneti fájlt -i, a kimenetet pedig -oként. De a fájlnevek természetesen megkülönböztetik a kis- és nagybetűket. Ha az -i-t használja, akkor a fájl végére írja be:

GO
EXIT

Ha a végén nincs EXIT, akkor az sqlcmd tovább fog várni a bevitelre, és ha korábban EXIT nem fogja GO, akkor az utolsó parancs nem fog működni. A kimeneti fájl tartalmazza az összes kimenetet, kiválasztásokat, üzeneteket, nyomtatást stb.

Az Invoke-SqlCmd az eredményt DataSet, DataTables vagy DataRows formájában állítja elő. Ezért, ha feldolgozza egy egyszerű kiválasztás eredményét, használhatja sqlcmd, miután elemezte a kimenetét, szinte lehetetlen valami összetettet levezetni: erre van Invoke-SqlCmd. De ennek a csapatnak is megvannak a maga poénjai:

  • Ha fájlt visz át neki a következőn keresztül -InputFile, Akkor EXIT nem szükséges, sőt szintaktikai hibát produkál
  • -Kimeneti fájl nem, a parancs objektumként adja vissza az eredményt
  • Két szintaxis létezik a szerver megadására: -ServerInstance -Felhasználónév -Jelszó -Adatbázis és azon keresztül -ConnectionString. Furcsa módon az első esetben nem lehet más portot megadni, mint 1433.
  • szövegkimenet, írja be a PRINT parancsot, amelyet egyszerűen „elkapott” sqlcmdmert Invoke-SqlCmd probléma
  • És ami a legfontosabb: Valószínűleg a Linux nem rendelkezik ezzel a parancsmaggal!

És ez a fő probléma. Csak márciusban ez a cmdlet elérhetővé vált nem Windows platformokon, és végre mehetünk előre!

Változó helyettesítés

Az sqlcmd változót helyettesít a -v használatával, például így:

# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res 
  -v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmd

Az SQL-szkriptben helyettesítéseket használunk:

set @spid=$(spid)
set @age=$(age)

Szóval itt van. *nixben a változó helyettesítések nem működnek... Paraméter -v figyelmen kívül hagyva. U Invoke-SqlCmd figyelmen kívül hagyva - Változók. Bár magukat a változókat meghatározó paramétert figyelmen kívül hagyja, maguk a helyettesítések működnek – a Shell bármely változóját használhatja. Azonban megsértettek a változók, és úgy döntöttem, hogy egyáltalán nem függök tőlük, és durván és primitíven viselkedtem, mivel az SQL-szkriptek rövidek:

# prepend the parameters  
"declare @age int, @spid int" | Add-Content "q.sql"
"set @spid=" + $spid | Add-Content "q.sql"
"set @age=" + $age | Add-Content "q.sql"

foreach ($line in Get-Content "Sqlserver/Automation/KillSpid.sql") { 
  $line | Add-Content "q.sql" 
  }
$cmd = "/opt/mssql-tools/bin/" + $conn + " -i q.sql -o res.log"

Ez, amint érti, egy teszt már a Unix verzióból.

Fájlok feltöltése

A Windows verzióban minden művelethez audit is társult: lefuttattuk az sqlcmd-t, valamilyen visszaélést kaptunk a kimeneti fájlban, ezt a fájlt csatoltuk az audit lemezhez. Szerencsére az SQL szerver ugyanazon a szerveren működött, mint a Jenkins, valami ilyesmi volt:

CREATE procedure AuditUpload
  @id int, @filename varchar(256)
as
  set nocount on
  declare @sql varchar(max)

  CREATE TABLE #multi (filer NVARCHAR(MAX))
  set @sql='BULK INSERT #multi FROM '''+@filename
    +''' WITH (ROWTERMINATOR = '' '',CODEPAGE = ''ACP'')'
  exec (@sql)
  select @sql=filer from #multi
  update JenkinsAudit set multiliner=@sql where ID=@id
  return

Így teljesen lenyeljük a BCP fájlt, és betoljuk az audit tábla nvarchar(max) mezőjébe. Természetesen ez az egész rendszer szétesett, mert SQL szerver helyett RDS-t kaptam, és a BULK INSERT egyáltalán nem működik UNC-n keresztül egy fájl exkluzív zárolási kísérlete miatt, és az RDS-nél ez általában el van ítélve. a legelejét. Ezért úgy döntöttem, hogy megváltoztatom a rendszer kialakítását, soronként tárolva az auditálást:

CREATE TABLE AuditOut (
  ID int NULL,
  TextLine nvarchar(max) NULL,
  n int IDENTITY(1,1) PRIMARY KEY
  )

És írd ebbe a táblázatba így:

function WriteAudit([string]$Filename, [string]$ConnStr, 
     [string]$Tabname, [string]$Jobname)
{
  # get $lastid of the last execution  -- проскипано для статьи
	
  #create grid and populate it with data from file
  $audit =  Get-Content $Filename
  $DT = new-object Data.DataTable   

  $COL1 =  new-object Data.DataColumn; 
  $COL1.ColumnName = "ID"; 
  $COL1.DataType =  [System.Type]::GetType("System.Int32") 

  $COL2 =  new-object Data.DataColumn; 
  $COL2.ColumnName = "TextLine"; 
  $COL2.DataType =  [System.Type]::GetType("System.String") 
  
  $DT.Columns.Add($COL1) 
  $DT.Columns.Add($COL2) 
  foreach ($line in $audit) 
    { 
    $DR = $dt.NewRow()   
    $DR.Item("ID") = $lastid
    $DR.Item("TextLine") = $line
    $DT.Rows.Add($DR)   
    } 

  # write it to table
  $conn=new-object System.Data.SqlClient.SQLConnection 
  $conn.ConnectionString = $ConnStr
  $conn.Open() 
  $bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $ConnStr
  $bulkCopy.DestinationTableName = $Tabname 
  $bulkCopy.BatchSize = 50000
  $bulkCopy.BulkCopyTimeout = 0
  $bulkCopy.WriteToServer($DT) 
  $conn.Close() 
  }  

A tartalom kiválasztásához azonosító alapján kell kiválasztani, n (identitás) sorrendben.

A következő cikkben részletesebben kitérek arra, hogy mindez hogyan kölcsönhatásba lép Jenkinsszel.

Forrás: will.com

Hozzászólás