Back to Home

Deploy ASP.NET applications using symbolic links

deploy · symbolic links · asp.net

Deploy ASP.NET applications using symbolic links

    Hello.
    We are all a little nervous when installing a new release on the prod. There are many different technologies that allow us to facilitate this process and make it a little less nervous. One of such technologies, which UNIX engineers have long been chosen by, is the use of symbolic links , which allows minimizing the time of rolling the release and rollbacks to the previous release if "something went wrong" (c). This mechanism is also present in Windows, but for some reason it is not actively used. But in vain. This article is intended to correct this misunderstanding and make the process of rolling release more enjoyable.


    Frame from the film "Gentlemen of Fortune"

    In general, this technology can be applied not only to ASP.NET applications, but also to any deployments (for example, windows services). Here, ASP.NET deployed on IIS is used as an example because there are several rakes that had to be stepped on before this idea took off. With other applications should be simpler.
    First, let’s describe what the problem actually is. Typically, web application files on IIS are located in a folder c:\inetpub\wwwroot\{appName}and every time you deploy (for example, using msdeploy) we reread the old files with the new ones. This works great until we have a set of files from release to release that does not change, but only their contents change. If in the next release, for example, one of the libraries is replaced with another (with a different name), then during the deployment a new library appears, and the old one is not deleted, which often leads to epic files. Again, if suddenly something did not work for you and you want to quickly roll back to the previous release, another failure may wait for you, as the files from the new release will not be erased. Symbolic links elegantly solve this problem.

    We describe a specific example. Create a Releases folder on C: drive and add the releases of our application into it:
    C:\Releases\1.1\WebApp
    C:\Releases\1.2\WebApp
    

    And in the folder, c:\inetpub\wwwroot\create a symbolic link WebApp, which will look at the folder c:\Releases\1.1\WebAppand which at any time can be switched to c:\Releases\1.2\WebAppand back.

    You should immediately warn that your application must meet several parameters (which are perfectly described in 12 factors )
    1. The release folder should contain all the necessary set of libraries and files so that the application is fully functional. No implicit links.
    2. Your releases must be compatible with external resources (such as a database). Those. if you made changes to the database, then the application should remain functional both in the new and in the old release.

    So, to create a symbolic link in Windows, use the mklink command. In our case, with the / D switch, which means that this is a link to a directory.

    mklink /D  c:\inetpub\wwwroot\WebApp c:\Releases\1.1\WebApp
    

    Actually everything. You can make sure that the WebApp directory with a special icon appears in the wwwroot folder, and if you double-click on it, you will find yourself in the release folder 1.1. It remains to turn this folder in IIS into a Web-application by right-clicking on it and selecting the appropriate item. Go to the browser and make sure everything works.
    With the "switching" of the release there are some difficulties. The fact is that ASP.NET applications are compiled and written to the cache on first launch. In the future, IIS looks at the changes in the web.config file, and if they occur, it “rebuilds” the application. In the case of symbolic links, IIS does not see these changes, so if you just switch the symbolic link to a new directory, nothing will happen, the old release will work. In order for everything to work, you need to clear the cache. Google recommends doing this with the iisreset command, and this leads to a restart of the W3SVC service, which is too radical for us - we don’t want other applications on this server to restart. Fortunately, IIS allows you to restart not the entire service, but only the site itself or its pool. I must say right away - it was experimentally established that for some reason restarting the site is extremely unstable. He then reassembles the application, then no, then generally gets up in an unknown raskoryaku. But restarting the pool works very well.
    And one more nuance. The mklink command does not allow you to "switch" the link. Therefore, first you need to delete it with the rmdir command, and then recreate it but with a different path. So, in order to switch the link you need to perform the following procedure:
    1. Stop the pool
    2. Delete a symbolic link;
    3. Create a link with a new path;
    4. Run the pool.


    C:\> C:\Windows\System32\inetsrv\appcmd stop apppool DefaultAppPool
    C:\> rmdir c:\inetpub\wwwroot\WebApp
    C:\> mklink /D c:\inetpub\wwwroot\WebApp c:\Releases\1.2\WebApp
    C:\> C:\Windows\System32\inetsrv\appcmd start apppool DefaultAppPool
    


    This approach gives another tasty bun. For example, on IIS you can raise another site, say on port 8080. Then create a symbolic link for it with your new release. Thus, you will have the old release at 80, and the new at 8080. Now you can in a relaxed atmosphere check the new release, and then simply switch your main site (which is on port 80) to a new folder.

    In conclusion, I want to give a PowerShell script that automates this process:
    Script text
    param([string]$Release=$null,[string]$AppName=$null,[string]$AppPath="C:\inetpub\wwwroot\",[string]$ReleasesPath="C:\Releases\",[string]$PoolName="DefaultAppPool");
    if ($Release)
    {
        if($AppName)
        {
            Write-Output "Старт!";
            Write-Output "Останавливаем пул...";
            cmd /c "C:\Windows\System32\inetsrv\appcmd stop apppool $PoolName";        
            Write-Output "Обновление приложения $AppName.";
            $link = $AppPath+$AppName;
            Write-Output "Link: $link";
            $target=$ReleasesPath+$Release+"\"+$AppName;
            Write-Output "Target: $target";
            if(Test-Path $target)
            {
                # если символическая ссылку существует, то удаляем ее
                if (Test-Path $link)
                {
                    cmd /c "rmdir $link"
                    Write-Output "Удалили директорию $link";
                };
                # Создаем символическую ссылку
                cmd /c "mklink /D $link $target" 
            }
            else
            {
                Write-Error "Не найден путь: $traget";
            }
            Write-Output "Запускаем пул..."
            cmd /c "C:\Windows\System32\inetsrv\appcmd start apppool $PoolName";
            Write-Output "Ok!"          
        }
        else
        {
            Write-Error "Не задан параметр AppName";
        }
    }
    else
    {
        Write-Error "Не задан параметр Release!"
    }
    


    The following parameters are passed there:
    • Release - name of the folder with the release;
    • AppName - name of the application;
    • AppPath - the path to the application (where the symbolic link is located);
    • ReleasesPath - the way where the releases are;
    • PoolName is the name of the pool.


    And more ...
    PowerShell 5.0 now supports working with symbolic links . So if you install the fifth version and the powershell-control module of IIS ( PS> Import-Module WebAdministration), then you can do without cmd commands at all, having done everything with pure powershell.

    Powershell 5.0 Script Text
    param([string]$Release=$null,[string]$AppName=$null,[string]$AppPath="C:\inetpub\wwwroot\",[string]$ReleasesPath="C:\Releases\",[string]$PoolName="DefaultAppPool");
    if ($Release)
    {
        if($AppName)
        {
            Write-Output "Старт!";
            Write-Output "Останавливаем пул $PoolName...";
            Stop-WebAppPool $PoolName -Passthru;
            Write-Output "Обновляем приложения $AppName.";
            $link = $AppPath+$AppName;
            Write-Output "Link: $link";
            $target=$ReleasesPath+$Release+"\"+$AppName;
            Write-Output "Target: $target";
            if(Test-Path $target)
            {
                #создаем ссылку
                New-Item -ItemType SymbolicLink -Path $AppPath -Name $AppName -Value $target -Force
            }
            else
            {
                Write-Error "Не найден путь: $traget";
            }
            Write-Output "Запускаем пул..."
            Start-Sleep 3; #нужно немного подождать перед запуском :)
            Start-WebAppPool $PoolName -Passthru;
            Write-Output "Ok!"          
        }
        else
        {
            Write-Error "Не задан параметр AppName";
        }
    }
    else
    {
        Write-Error "Не задан параметр Release!"
    }
    

    Read Next