Спойлер – никак.
Как-то мне довелось админить большой домен на несколько тысяч пользователей. После того как база NTDS.DIT выросла до 14ГБ, поднятие нового контроллера домена без использования функционала Install From Media (IFM) стало занимать несколько часов.
Я создал новый тестовый домен, создал в нем сходное количество пользователей, групп и компьютеров. Назначил каждому пользователю картинку на 100к и…
Получил размер базы меньше 1ГБ. Это «Ж-ж-ж» неспроста! – подумал я и обратился в MS Premier Support.
Те проанализировали количество объектов с помощью утилиты DBAnalyzer и ничего криминального не нашли.
После этого с помощью esentutl они посмотрели размер базы:
«Ух ты» – сказали суровые сибирские мужики инженеры MS – «да у вас индексов многовато». А не эта ли у вас проблема?
После включения Credentials Roaming и сохранения закрытых ключей в AD на Windows7/Windows 2008R2 был баг: «левые» закрытые DPAPI-ключи продолжают сохраняться в AD. В результате, у пользователя может быть несколько тысяч закрытых DPAPI-ключей, попутно значительно растет размер базы AD. Хотите решить проблему – удалите все ключи! Выборочно? Нет, выборочно нельзя – необходимо вычистить все три атрибута, относящиеся к PKI, при этом пользователю все сертификаты, хранящиеся в AD, необходимо перевыпускать.
Мы грустно вздохнули. «Мы ж ключи используем во всяких документооборотах и 1Сках через терминальную ферму», и закопали стюардессу.
Прошел год, я развернул еще несколько контроллеров домена (часов за 6 каждый без IFM) и подумал, что, пожалуй, стоит выкопать стюардессу.
Мы начали разбираться, что можно сделать.
Во-первых, мы пропатчили 100500 терминальных серверов. Это помогло остановить дальнейший рост AD, но зачистить базу не помогло.
Во-вторых, мы включили на всех контроллерах домена информацию о процессе сборщике мусора. Для этого в ветке HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics надо найти параметр Garbage Collection и задать ему значение 1.
После этого в журнале Active Directory каждые 12 часов будет появляться событие с кодом 1646, содержащее текущие размеры базы:
Free hard disk space (megabytes), также известный как whitespace:
948
Total allocated hard disk space (megabytes):
14967
В-третьих, нам потребовался вывод утилиты repadmin /showobjmeta по пользователям, указанным через DN. Это дало нам возможность понять – сколько ключей есть у пользователя (и осознать масштаб проблемы).
В выводе также указывается статус ключа: ABSENT или PRESENT.
Инженеры MS Premier Support Инопланетяне подготовили скрипт dumpLVR.cmd, выгружающий список ключей пользователя в файл. Отсортировав список файлов по размеру, стало возможно охватить взором масштаб проблемы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
dsquery user -limit 0 > %temp%\userDN.txt md %temp%\LVRs md %temp%\LVRs\Statistics FOR /F "tokens=*" %%i IN (%temp%\userDN.txt) DO ( CMD /C echo > "%temp%\LVRs\%%~i.txt" IF EXIST "%temp%\LVRs\%%~i.txt" Repadmin /showobjmeta . %%i | findstr "PRESENT ABSENT" > "%temp%\LVRs\%%~i.txt" ) FOR /F "tokens=*" %%i IN ('DIR /b %temp%\LVRs\*.txt') DO ( FIND /c "PRESENT" "%temp%\LVRs\%%i" >> %temp%\LVRs\Statistics\All_PRESENT_LVRs.txt FIND /c "ABSENT" "%temp%\LVRs\%%i" >> %temp%\LVRs\Statistics\All_ABSENT_LVRs.txt FIND /c "msPKIDPAPIMasterKeys" "%temp%\LVRs\%%i" >> %temp%\LVRs\Statistics\All_msPKIDPAPIMasterKeys.txt FIND /c "msPKIRoamingTimeStamp" "%temp%\LVRs\%%i" >> %temp%\LVRs\Statistics\All_msPKIRoamingTimeStamp.txt FIND /c "msPKIAccountCredentials" "%temp%\LVRs\%%i" >> %temp%\LVRs\Statistics\All_msPKIAccountCredentials.txt ) start %temp%\LVRs\Statistics\ Echo Done. PAUSE |
В принципе, вы просто можете выполнить по всем пользователям дамп в текстовый файл команды repadmin /showobjmeta. Будет то же самое, что в скрипте.
После этого я зачистил сотруднику ключи и… увидел то же, что и вы на скриншоте:
– ключей стало вдвое больше;
– часть ключей получили статус ABSENT (т. е. в учетке их нет, но они лежат на кладбище и продолжают занимать место);
– база подросла.
Зато мы поняли – ключи возвращаются назад, если их удаление произошло при активном сеансе на терминальном сервере, так как включен Credentials Roaming, который при выходе из системы выгружает все актуальные ключи в базу AD.
Выполнив принудительное завершение сеанса пользователя на терминальном сервере перед зачисткой и частичную чистку профиля пользователя, мы получили перевод всех ключей в статус ABSENT! Это уже был успех.
В-четвертых, мы снизили до 9 дней срок хранения объектов на «кладбище», чтобы ускорить их удаление из базы.
Set-ADObject -Identity “CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=firstbank,DC=local” -Partition “CN=Configuration ,DC =COM” -Replace @{tombstoneLifetime=’9′}
Обратите внимание: удаленный объект сначала хранится в корзине, а потом на кладбище, вследствие чего срок хранения объекта составляет 2*Tomb.
Как я увидел дальше – Garbage Collection не всегда успевал зачистить все объекты за один проход, иногда приходилось ждать несколько дней.
В-пятых, мы занялись инвентаризацией используемых систем. Взяли отключенных пользователей и тех, кто 100% не пользуется системами с PKI. А инженеры MS инопланетяне составили очередной скрипт по зачистке ключей по списку пользователей – delete_credentails_From_File.vbs. Мы зачистили эти ключи (предварительно завершив терминальные сессии) и через 18 дней получили рост свободного места в базе.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
'Option Explicit Dim adoCommand, adoConnection, strFilter, strAttributes Dim resRecSet, strSrchPath, strDefNamCont, objRootDSE, strQuery Dim strdstName, objUser ' Setup ADO objects. Set adoCommand = CreateObject("ADODB.Command") Set adoConnection = CreateObject("ADODB.Connection") adoConnection.Provider = "ADsDSOObject" adoConnection.Open "Active Directory Provider" adoCommand.ActiveConnection = adoConnection ' Search entire Active Directory Set objRootDSE = GetObject("LDAP://RootDSE") strDefNamCont = objRootDSE.Get("defaultNamingContext") strSrchPath = "<LDAP://" & strDefNamCont & ">" i = 0 l = 0 Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.OpenTextFile("c:\tmp\testfile.txt", 1, True) Set Donefile = objFSO.OpenTextFile("c:\tmp\Del_Cred_Result_log.txt", 8, True) DoneFile.Writeline "===================================" DoneFile.Writeline Now DoneFile.Writeline "_______________________________________" Do Until objFile.AtEndOfStream ReDim Preserve FileLine(i) FileLine(i) = objFile.ReadLine i = i + 1 Loop objFile.Close For l = LBound(FileLine) to UBound(FileLine) Step +1 UserFromList = FileLine(l) strFilter = "(&(objectCategory=person)(objectClass=user)(samAccountName="&UserFromList &"))" ' Comma delimited list of attribute values to retrieve. strAttributes = "distinguishedName" ' Construct the LDAP syntax query. strQuery = strSrchPath & ";" & strFilter & ";" & strAttributes & ";subtree" adoCommand.CommandText = strQuery ' Run the query. Set resRecSet = adoCommand.Execute Do Until resRecSet.EOF strdstName = resRecSet.Fields("distinguishedName").value WScript.Echo "User Distinguished Name: " & strdstName Set objUser = GetObject("LDAP://" & strdstName) objUser.PutEx 1,"msPKIAccountCredentials",NULL objUser.PutEx 1,"msPKIRoamingTimeStamp",NULL objUser.PutEx 1,"msPKIDPAPIMasterKeys",NULL objUser.SetInfo '''WScript.Echo "============================================================================" '''WScript.Echo "The user properties msPKIAccountCredentials, msPKIRoamingTimeStamp and msPKIDPAPIMasterKeys cleared for the user accout " & strdstName '''WScript.Echo "============================================================================" & vblf DoneFile.Writeline strdstName resRecSet.MoveNext Loop Next WScript.Echo "Done!!!" Wscript.Quit(0) |
Это был прямо успех-успех!
В принципе, вместо этого скрипта вы можете использовать свой. Смысл – зачистка атрибутов msPKIAccountCredentials, msPKIRoamingTimeStamp и msPKIDPAPIMasterKeys у заранее указанного списка пользователей.
В-шестых, началась грустная рутина. Сначала мы еще раз прошерстили пользователей 1С и смогли вычистить еще ряд пользователей. Но оставался документооборот (ДО), в котором были ключи у 40% пользователей – толстячков ☹
К счастью, админы документооборота предложили отличную идею: просмотреть логи приложения на предмет исходного IP-адреса. Если адрес был локальный (т. е. пользователь запускал клиента ДО на своем ПК), то его ключ можно было смело чистить.
Таким образом, мы смогли зачистить эти ключи примерно у 90% учетных записей и освободить в базе 8820МБ. Ииихаа.
В-седьмых, началась рутина по офлайн-дефрагментации базы AD, которую было необходимо сделать на каждом контроллере.
- Net stop ntds
- Ntdsutil
- Activate instance ntds
- Files
- Compact to c:\temp
- Q
- Q
- copy “c:\temp\ntds.dit” “C:\Windows\NTDS\ntds.dit”
- del C:\Windows\NTDS\*.log
- net start ntds
Как вы думаете, сколько будет 14967–8820?
3900МБ, да-да. В старой таблице индексов было много ссылок на удаленные объекты. Так как офлайн-дефрагментация пересоздала таблицу индексов, размер базы стал еще меньше, чем мы рассчитывали!
И вот это уже был прям успешище!
Ну и надо было вернуть назад срок хранения удаленных объектов.
Столь малый размер БД позволил нам не только уменьшить время на развертывание (развертывание через IFM занимало примерно столько же времени), но и добиться ряда других бонусов:
– при полномочном восстановлении и последующей репликации AD процесс увеличения USN уже бы не занял несколько часов;
– для оптимальной производительности AD базу требовалось кэшировать в памяти. Снижение на 10ГБ размера базы на каждом RWDC и на 12ГБ на каждом RODC позволило сэкономить только на памяти пару десятков тысяч долларов.
Отдельное спасибо хочется сказать моим бывшим руководителям, у которых хватило мужества пройти со мной этот путь до конца, отстаивая необходимость этих мероприятий.
Спасибо за отличный детектив! Читается на одном дыхании!