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?
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-SqlCmdprobléma
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:
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.