sobota, 24 września 2011

Automatyczna aktualizacja projektów na komputerach sieciowych

Zainspirowany blogami innych osób, oraz za sugestią znajomych postanowiłem, że będę się dzielił nabytą wiedzą ze światem. Wielokrotnie każdy z programistów, który zajmuje się branżą zawodowo napotykał na swojej drodze nietypowe problemy, niespotykane nigdzie dotąd. Wydaje mi się, że warto publikować tego typu "łamigłówki" nie tylko prowadząc dziennik dla siebie, ale żeby zbudować społeczeństwo, które nie jest dla siebie konkurencją, lecz bazą wiedzy, która pozwoli błyskawicznie rozwiązać dany problem obierając optymalną drogę. Na pierwszy ogień chciałbym przedstawić moją propozycję problemu zdalnego debuggowania aplikacji :

ZDALNY DEBUGGING - AUTOMATYZACJA PRACY

  1. Omówienie problemu
  2. Proponowane rozwiązanie
  3. Parę słów o debuggowaniu aplikacji zdalnie
  4. Techniczne szczegóły
OMÓWIENIE PROBLEMU

Z pewnością wielu programistów, głównie aplikacji desktopowych spotkało się z problemami wdrożeniowymi aplikacji. Niby wszystko ładnie, pięknie - testy wykazują, że nie ma błędów, a tu przy instalacji aplikacji na serwerze produkcyjnym wszystko się sypie. Spotkałem się już kilkukrotnie z sytuacją, w której  do aplikacji dorzucane są Message Boxy, które wyświetlają na ekranie pewne informacje techniczne, naprowadzające na przyczynę błędu. Uważam, że jest to bardzo nieeleganckie, oraz czasochłonne rozwiązanie. Dodatkowo jest duża szansa, że nie pomoże nam to znaleźć źródła problemu. Inną, lepszą formą, jest architektura aplikacji oparta na rejestrowaniu zdarzeń. Podsłuchując i przekierowując zdarzenia do pliku mamy wgląd w procesy zachodzące w programie. To jest już dużo lepsze rozwiązanie, jednak przydatne głównie wtedy, gdy aplikacja się sypie poza środowiskiem testowym -czyli u klienta. Dodatkowo to raczej pomaga wskazać, że jest błąd, ale nie wiadomo do końca jaki. Uważam, że szybszym, i zarazem najefektywniejszym sposobem jest zdalny debugging. Dzięki narzędziu "Remote Debugger" udostępnionym wraz ze środowiskiem Visual Studio jesteśmy w stanie podpiąć się pod proces naszej dllki zdalnie. Aplikacja zostanie zsynchronizowana z naszym środowiskiem, dzięki czemu będziemy mogli ją w "klasyczny" sposób debuggować. (jeśli na serwerze, w którym dokonujemy zdalnego debuggowania nie ma bibliotek VS, to nie będziemy mieli niektórych funkcjonalności - tj. podgląd danych z typu DataTable). Jednak metoda ta również bywa irytująca - za każdym razem, jak przebudowujemy nasz projekt, musimy wgrać na serwer produkcyjny nowe, zaktualizowane dllki. Dodatkowo za każdym razem musimy się łączyć z serwerem, a następnie podpinać pod proces. Jeśli jeszcze serwer produkcyjny nie jest osadzony na dobrym łączu ... tracimy cenny czas. 

CEL

Chciałbym zaproponować rozwiązanie, które dokona enkapsulacji całego procesu od przerzucenia plików na serwer produkcyjny, po podłączenie pod proces zdalny/lokalny. Efektem ma być możliwość zdalnego debuggowania, które kosztuje nas tyle samo pracy, co zwykły debugging. (Przyciśnięcie jednego przycisku)

REMOTE DEBUGGER

Zanim przejdę do przedstawienia rozwiązania, omówię jeszcze samą aplikację "Remote Debugger". Tak jak wcześniej napisałem, dzięki tej aplikacji jesteśmy w stanie podpiąć się pod proces nie znajdujący się na naszej maszynie, który następnie możemy debuggować klasycznymi metodami. Domyślnie aplikacja ta jest dostępna :
  • Z Menu Start : Microsoft Visual Studio <numer>\Visual Studio Tools\Visual Studio Remote Debugger
  • Z dysku : <ścieżka do VS>\Common7\IDE\Remote Debugger
  • Jeśli nie posiadamy aplikacji, to można skorzystać z linków Visual Studio 2010 lub Visual Studio 2008
Remote Debugger uruchamiamy na serwerze, w którym znajduje się nasza aplikacja. (mamy do wyboru aplikację x86 lub x64 - należy pamiętać by wybrać wersje odpowiadającą systemowi). Do celów prezentacyjnych odpaliłem wirtualną maszynę z systemem WindowsXP, na którym będę miał aplikację wyświetlającą "Hello World". Spróbujemy dokonać debuggowania tej aplikacji z serwera roboczego.
W tym celu zrobiłem przykładową aplikację konsolową, którą nazwałem "HelloRemote" :

        static void Main(string[] args)
        {
            //Ta linia została dodana, aby zatrzymać działanie aplikacji, na 
            //czas przyłączenia procesu do środowiska Visual Studio
            Console.ReadKey();
 
            Console.WriteLine("Hello Remote World !!!");
 
            Console.ReadKey();
        }

 
Następnie pliki dll, exe i (WAŻNE) .pdb (pliki umożliwiające nam debugging) przerzuciłem na serwer produkcyjny. Pora nawiązać połączenie z serwerem.

Przed uruchomieniem aplikacji "Remote Debugger" na serwerze produkcyjnym należy pamiętać o kilku rzeczach :
-Wszystkie zapory muszą być odblokowane dla tej aplikacji (na szczęście przy uruchomieniu pojawi się pytanie, czy odblokować zaporę dla tego procesu)
-Na serwerze produkcyjnym i roboczym powinien być zalogowany ten sam użytkownik, znajdujący się w tej samej domenie, inaczej będziemy mogli mieć wgląd tylko w kod "Niezarządzany" - tracąc przy tym cenną możliwość debuggowania procesu. Na szczęście da się to obejść : domyślnie w systemie windows jest włączona opcja, która mówi, że  każdy użytkownik sieciowy, który loguje się do systemu traktowany jest jako gość. Należy zmienić tą opcję na mówiącą, że użytkownik logujący się do systemu zachowuje dane swojego konta - przez co będzie możliwość obejścia różnicy domen w przypadku systemów innych niż Windows Server. Aby to zrobić, należy w Panelu Administracyjnym wejść w "Local Security Policy" (w przypadku systemu PL, musicie znaleźć polski odpowiednik) -> Local Policies -> Security Options -> Network access : Sharing and security model for local accounts. Tam należy ustawić: Classic - local users authenticate as themselves.
-Serwer produkcyjny musi być widoczny dla serwera roboczego

Dobra. Uzbrojeni w informacje możemy uruchomić program. Na początku możemy dostać dwa komunikaty. Jeden z nich się pojawia przy pierwszym uruchomieniu 32-bitowej aplikacji, i jest to ostrzeżenie, że działa ona tylko na 32 bitowych systemach. Po prostu wciskamy "Tak".
Oraz drugi:
To jest już wyżej wspomniany komunikat, który odblokuje zapory dla aplikacji, dla komputerów z sieci, lub wszystkich (należy wybrać opcję 2 lub 3).

W tym momencie aplikacja już działa. Warto jeszcze jednak wiedzieć, że w Tools->Options wybieramy rodzaj uwierzytelniania. Mamy do wyboru :

  • Windows Authentication (ten nas interesuje, ze względu na możliwość pracy z kodem zarządzanym) - uwierzytelnianie poprzez system Windows. Aby połączyć się poprzez tą opcję musimy mieć tych samych użytkowników zarówno na serwerze produkcyjnym, jak i roboczym (ten sam login, hasło, i domena (wcześniej opisałem co zrobić, gdy nie możemy utworzyć własnej domeny). W moim przypadku na serwerze produkcyjnym (wirtualnym) mam użytkownika "Norbert" z nazwą komputera "Lukasz-VM", i hasłem "qwe123", a na serwerze roboczym użytkownika "Norbert", z nazwą komputera "Norbert-PC" i hasłem "qwe123"
  • No Authentication (Native Only) - każdy, kto połączy się z tym komputerem poprzez wybrany port może podpiąć się pod proces systemu. Problem polega na tym, że procesy są widocznie jako niezarządzane, przez co nie mamy interesujących nas funkcjonalności Debuggowania.
Wybieramy Windows Authentication. W konsoli powinien się pojawić adres, który należy wpisać na serwerze roboczym, aby się podpiąć pod proces. W moim przypadku :
"Mvsmon started a new server named 'Norbert@LUKASZ-VM'" mówi, że adres to : 'Norbert@LUKASZ-VM' (za LUKASZ-VM można również wpisać IP)
Aplikacja jest gotowa do działania. Teraz wystarczy uruchomić aplikację, którą będziemy debuggować :



Teraz pora na podpięcie się pod proces ze środowiska na serwerze roboczym. Ja używam Visual Studio 2010. Do dzieła : Debug -> Attach to Process.
W zakładce "transport" :

  • Remote (Native only with no authentication) oznacza, że podpinamy się pod proces zdalny, bez uwierzytelnienia - inaczej mówiąc bez możliwości debuggowania
  • Default - pozostałe przypadki
Wybieramy "Default".
W zakłądce Qualifier wpisujemy wyżej wspomnianą nazwe użytkownika + serwera
Na dole powinny się wyświetlić wszystkie procesy uruchomione dla tego użytkownika. W moim przypadku wybieram "HelloRemote":
Po kliknięciu  Attach powinien się nam uruchomić debugger.
Jeśli wszystko poszło jak należy, to powinna się dokonać synchronizacja z procesem na serwerze produkcyjnym, dzięki czemu mam dostęp do Debuggowania aplikacji !

TECHNICZNE SZCZEGÓŁY
Mam nadzieję, że wystarczająco dobrze wyczerpałem temat zdalnego debuggowania. Teraz spróbujmy usprawnić jeszcze bardziej cały proces ! Tak jak obiecałem, pokażę, jak przy pomocy jednego kliknięcia uzyskać cały efekt opisany w dziale "Remote Debugger". 
  • Przenoszenie plików na serwer produkcyjny
Do tego celu najlepiej się nada : Project -> Properties -> Build Events
W Post-Build event command line można umieścić skrypt korzystający z wiersza poleceń systemu windows, który się uruchomi po całym procesie przebudowy aplikacji. Umieśćmy tam następujący skrypt :

net use k: \\<nazwa serwera produkcyjnego>\<ścieżka projektu>
copy "$(TargetDir)*.dll" k:\
copy "$(TargetDir)*.pdb" k:\
copy "$(TargetPath)" k:\

Należy również na serwerze produkcyjnym udostępnić sieciowo podaną ścieżkę projektu. Powyższy skrypt po procesie przebudowy przerzuca wszystkie pliki dll,pdb, oraz samą aplikację na podaną ścieżkę serwera produkcyjnego.
  • Automatyczne podpięcie się pod zdalny proces
Do tego celu napiszemy makro (View -> Other Windows -> Macro Explorer)
Kolejne kroki :
  1. W Macro Explorer, klikamy PPM na zakładkę "MyMacros", i wybieramy New module
  2. Nazywamy moduł "ZdalnePolaczenie" i zatwierdzamy
  3. Klikamy dwukrotnie w Macro Explorerze na moduł ZdalnePolaczenie. Powinien nam się uruchomić edytor makr z wstępnie wygenerowanym kodem.
  4. Do modułu dodajemy poniższy skrypt :
Sub Zdalne()
        Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger
        Dim trans2 As EnvDTE80.Transport =
        dbg2.Transports.Item("Default")
        Dim proc2 As EnvDTE80.Process2

        Dim dbgeng(1) As EnvDTE80.Engine
        dbgeng(0) = trans2.Engines.Item("Managed")

        proc2 = dbg2.GetProcesses(trans2, "<nazwa serwera, np 
        Norbert@Lukasz-VM>").Item(<nazwa procesu>)
        proc2.Attach2(dbgeng)
End Sub

Skrypt ten podpina nas pod proces <nazwa procesu> znajdujący się na serwerze <nazwa serwera>. Możemy skrypt sparametryzować. W tym celu na wejście funkcji wprowadzamy parametry z przedrostkiem Optional (przedrostek ten jest potrzebny ze względu na to, że domyślnie makra nie powinny mieć parametrów - to jest obejście reguły). Makro jest gotowe ! Możemy jeszcze zamieścić je w wygodnym przycisku. W tym celu np. kliknijmy PPM na ToolBar, i wybierzmy zakładkę Customize. Otworzy nam się nowe okno dialogowe. W "Toolbars" kliknijmy New, i utwórzmy zakładkę Zdalne. W Commands zaznaczmy "Combo Box" Toolbar, i wybierzmy "Zdalne". Następnie naciśnijmy na "Add Command..." i wybierzmy Macros -> Macros.MyMacros.ZdalnePolaczenie.Zdalne. Zatwierdźmy. Wprowadźmy jeszcze czytelniejszą nazwę makra - w tym celu klikamy na "Modify Selection" i w sekcji "Name" wpisujemy np. "Zdalne". Gotowe ! Powinniśmy uzyskać taki efekt :


W tym momencie klikając Rebuild automatycznie załączamy wszystkie pliki na serwer, a klikając przycisk "Zdalne" podpinamy się pod aplikację, i możemy rozpocząć Debuggowanie.
  • Automatyczne uruchomienie aplikacji na serwerze produkcyjnym
Bardzo dobrym pomocnikiem w rozwiązaniu tego problemu okazuje się Ta aplikacja. PsExec jest komendą, która pozwala uruchomić dowolny proces zdalnie. Możemy np. uruchomić zdalnie aplikację "cmd", przez co uzyskujemy dostęp do praktycznie każdej funkcjonalności systemu. My jednak chcielibyśmy uruchomić tylko aplikację testową:

PsExec \\<Nazwa serwera lokalnego> -u <Nazwa użytkownika> -p <Haslo> -d -i "<Ścieżka aplikacji">

Ten skrypt daje nam taką możliwość. Jeśli dodamy teraz tą ścieżkę do Post-Build, podczas każdego rebuilda dodatkowo uruchomimy naszą aplikację testową - oszczędzamy kolejne cenne kwanty czasu :)
  • Połączenie wszystkiego w jedną całość
Tak jak mówiłem, cały ten proces miał być obsługiwany przez jeden przycisk. W tym celu należałoby do naszego makra dodać jeszcze automatyczny rebuild, który uruchomi skrypt "Post-Build", po czym wykona wcześniej zamieszczoną logikę. Poniższa linijka robiłaby wszystko, o co nam chodzi:
DTE.ExecuteCommand("Build.RebuildSolution")
Ale... Niestety "ExecuteCommand" wykonuje się asynchronicznie, w związku z tym kod poleci dalej nie czekając, aż przebudowa solucji się skończy. Na szczęście można temu zaradzić. W module z makrami znajduje się plik "EnvironmentEvents" - gdzie jest podpięcie pod wszystkie zdarzenia, które mają miejsce w IDE. Daje nam to ogromne możliwości ! Dzięki temu możemy podpiąć się pod zakończenie procesu budowy projektu, gdzie automatycznie odpalimy wcześniej napisane makro. W tym celu musimy dokonać pewnej przeróbki kodu :
W pliku EnvironmentEvents dodajemy poniższy fragment kodu :

Private Sub PublishEvents_OnPublishDone(ByVal Scope As EnvDTE.vsBuildScope, ByVal Action As EnvDTE.vsBuildAction) Handles BuildEvents.OnBuildDone
        DTE.ExecuteCommand("Build.RebuildSolution")
        Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger
        Dim trans2 As EnvDTE80.Transport = 
        dbg2.Transports.Item("Default")
        Dim proc2 As EnvDTE80.Process2
        Dim dbgeng(1) As EnvDTE80.Engine
        dbgeng(0) = trans2.Engines.Item("Managed")
        proc2 = dbg2.GetProcesses(trans2, "<Serwer wpisywany
        podczas podpinania się pod proces>").Item("<nazwa procesu
        (pełna ścieżka)>")
        proc2.Attach2(dbgeng)
End Sub

Inaczej mówiąc - przenieśliśmy nasz skrypt do Handlera zdarzenia BuildEvents.OnBuildDone (czyli do handlera, które się uruchamia, po przebudowie całej solucji). Jako ciekawostka BuildEvente posiada 4 handlery :
  1. OnBuildBegin - wykonujący jeden raz, gdy rozpoczyna się przebudowa solucji
  2. OnBuildDone - wykonujący jeden raz, po zakończeniu przebudowy solucji
  3. OnBuildProjConfigBegin - wykonujący się tyle razy, ile przebudowanych jest projektów w solucji (rozpoczęcie)
  4. OnBuildProjConfigDone - analogicznie, przy zakończeniu przebudowy każdego projektu
Post Build :

net use k: \\<serwer>\<ścieżka do folderu aplikacji>
copy "$(TargetDir)*.dll" k:\
copy "$(TargetDir)*.pdb" k:\
copy "$(TargetPath)" k:\
start "$(TargetDir)"PsExec  \\<serwer> -u <użytkownik> -p <hasło> -d "<ścieżka do aplikacji>"

Teraz, ilekroć będziemy przebudowywać nasz projekt - automatycznie przeniesiemy wszystkie pliki na serwer produkcyjny, uruchomimy testowany proces, po czym się pod niego zdalnie pdepniemy, i jesteśmy gotowi do debuggowania.
Bomba !!!

Brak komentarzy:

Prześlij komentarz