Wednesday, July 8, 2020

Deploy Angular and spring boot apps to Azure app service or storage account

1. Deploy angular web app as static html web app
One typical web project structure is Angular (front SPA web app) communicating with backend Spring boot (API function app), and deployed to Azure cloud. The app can use Azure Active Directory or Azure B2C for user authentication.

To demo the various options for this architecture, a simple prototype web application will be created and deployed to Azure.

1. Create Angular SPA app with git repository
Create a new angular app with two button, one for login and one for sending a request to service api app. For now both button just logs a console log.
Add the project into a new git repository, so it can be deployed to azure from git using the Azure Static Web App service. The git repository is at https://github.com/lijo2/angularspa

2. Deploy the angular project to Azure 

2.1 Option 1 - Host static html web site with app service
2.1.1 First create an app service plan , for example, haiquanserviceplan, and a resource group, for example, angularwebapp. 

2.1.2 Second, from azure portal, find the storage account created by Azure for supporting cloud shell bash, the storage account name starts with "cs". In storage account's file service, create a new directory, and copy the angular's production build's dist folder to this new directory. In this example, all html and js files are copied to a directory called "spa" in the storage account.
Note, the azure cloud shell only provides function to upload/download a single file, so it cannot be used to upload a folder in this case.

2.1.3 open azure cloud shell from azure portal, and cd into the new directory just created in 2.2, the folder should contain the html and js files used by the static web app, then run the below command to create the static web app
az webapp up --location eastus --name spawebappli --html -p haiquanserviceplan -g azuretest

The above command will create a new web app containing static html and js resource in the resource group, if later, you need to update the resource or include some sub folder, then you can send the web app resource as a zip file by using az webapp deployment source config-zip command, or using a FTP client. 
az webapp deployment source config-zip -g azuretest -n spawebappli --src app.zip
-g: resourcegroup name
-n: app name

Note for creating zip file with npm command:
npm package 'npm-build-zip' can be installed and automatically zip the dist folder to generate a zip file with npm
 npm install npm-build-zip

Update the scripts in package.json to automatically generate the zip file and deploy to azure app service.
    "zip": "npm-build-zip --source=dist/angularspa --destination=dist",
    "deploy": "az webapp deployment source config-zip -g azuretest -n spawebappli --src dist/angularspa_0.0.0.zip"
 
To create a new deployment, run from visual studio code terminal:
npm run zip
npm run deploy

2.1.4 browser to the new app's url to verify the html web app has been created properly

2.1.5 Using FTP to update and upload/download web resource
Azure web app allows FTP access to the web application's site/wwwroot/folder. For this purpose, you will need to first install FTP client (for example, WinSCP). The ftp url and username/password are available in Azure portal's app service's deployment/deployment Center/FTP page.
 
2.2 Option 2 - host static web site in Azure storage account
2.2.1 Create a storage account 
2.2.2 In storage account's Setting/Static Website blade, enable static website
2.2.3 Download and install Azure Storage Explorer to upload the static website's resource files from local to storage account's $web container as shown below. You can also do so with azcopy from command line. Azure Storage Explorer can easily create sub folder, and upload files to sub folder.
2.2.4 Test the static web site from the url shown in storage account's Setting/Static Website blade
3. Deploy spring boot app to azure app service

As azure already created a maven plugin for web app, so it can be used to deploy the spring boot project to azure as web app.

3.1 create and test the spring boot project in localhost with 
mvn spring-boot:run

3.2 run  to create the jar file
mvn package

3.3 run 
mvn com.microsoft.azure:azure-webapp-maven-plugin:1.9.0:config
to config azure maven web app plugin by entering the appname, service plan, pricingtier and other information.

3.4 update pom file's azure mvn plugin setting to specify a port number 
   <plugin> 
        <groupId>com.microsoft.azure</groupId>  
        <artifactId>azure-webapp-maven-plugin</artifactId>  
        <version>1.9.0</version>  
        <configuration> 
          <schemaVersion>V2</schemaVersion>  
          <resourceGroup>azuretestspring</resourceGroup>  
          <appName>springapitestli</appName>  
          <pricingTier>B1</pricingTier>  
          <region>eastus</region>  
          <runtime> 
            <os>linux</os>  
            <javaVersion>java11</javaVersion>  
            <webContainer>java11</webContainer> 
          </runtime>  
          <appSettings> 
            <property> 
              <name>JAVA_OPTS</name>  
              <value>-Dserver.port=80</value> 
            </property> 
          </appSettings>  
          <deployment> 
            <resources> 
              <resource> 
                <directory>${project.basedir}/target</directory>  
                <includes> 
                  <include>*.jar</include> 
                </includes> 
              </resource> 
            </resources> 
          </deployment> 
        </configuration> 
      </plugin> 
3.5 run
mvn azure-webapp:deploy
to deploy the web app to azure app service plan.

3.6 after the spring boot app is updated, you will need to run
mvn package
mvn azure-webapp:deploy
to update the azure's deployment.

4. Send request from Angular project to sprint boot project
After deploying both angular and spring boot project to azure, the next step is enabling angular project to send xmlhttprequest to spring boot project. The following example shows how to send simple xhr request when a button is clicked in Angular project
  onClick() {
    console.log('call backend api to get data');
    const str = this.textField.value;
    this.http.get('https://springapitestli.azurewebsites.net/jsonapi/' + str).subscribe((resp) => {
      this.textField.setValue(JSON.stringify(resp));
    });
  }
Note, as angular project and spring boot project are deployed to different root url in azure, so sending xhr request from angular project to spring project will fail by default due to CORS. To make it work, the azure app service for spring boot project must add the angular app's url to its allowed CORS url. Alternatively, spring boot annotation of @ can also be used to specify allowed CORS origin headers.

Friday, June 26, 2020

Common Docker, Dockerfile and Bash commands

Docker commands (in bash)

docker images
list all images available on local

docker rmi imageNameOrID
docker rmi imagesNameOrID -f 
remove a docker image from local
using -f to force remove image even if it is attached to some containers 

docker build -t imagename .
docker build-t imagename:version .
build a docker image and give it a specified name using the dockerfile in the current directory.
-t to tag the image to a particular name and version

docker ps -a -q
docker container ls
list all running containers, -a (all) to show all container, by default, only running container shows. -q only show container ID.
 

docker ps -a
docker container ls -a
list all running and stopped containers

docker kill containername
kill a running container by name

docker rm containername -f
remove a container (using -f option to force to remove running container)

docker run --name ContainerName -d -p 8000:80 Imagename
run a nginx image of imagename in detached mode (-d), with host port (-p)8000 maps to nginx container's default port 80. By default, the nginx listens on port 80 after starting the container.  Assuming the index.html is in the host's current folder, then it can be loaded from browser with the below link

docker run --rm -it imagename bash
--rm :automatically remove container when it exits
-it bash: interactively start a bash command console, start the container and also open a bash command console, 

docker start containername
docker stop containername
docker restart containername
start, stop, restart a container by name

docker attach containername
attach docker container to std to see the container's console output, using ctrl+c to exit.

docker exec -it containername /bin/bash
docker exec containername bashcommand
attach to container's bash console to run bash command inside container, using exit command to exit.
Once enter the bash command console, you can install other required software, for example, installing vim as below:
apt-get update
apt-get install vim
using below exec command to show all environment variables
docker exec containername env

docker cp container:SrcPath hostDestPath
docker cp hostSrcPath container:destPath
copy files between container and host


docker stop $(docker ps -aq)
stop all containers

docker rm $(docker ps -aq)
delete all containers

docker rmi $(docker images -q)
delete all docker images

Steps to push local docker image to docker hub
1. First login to docker.com using your docker id and password
docker login

2. Tag the local image with the namespace of your docker userid
docker image tag myimage:mytag myDockerUserID/myimage:mytag

3. push the tagged image to docker hub
docker image push myDockerUserID/myimage:mytag

DockerFile commands:

FROM nginx
FROM node:
download the base image from dockerhub or local

COPY . /usr/share/nginx/html
copy the static files from the current directory to the specified folder in docker container. The above sample copies files to container's /usr/share/nginx/html folder. 

ADD https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe /temp/python-3.5.1.exe
copy files from source folder or external url to the destination folder in the container

WORKDIR directoryName
change the current directory in container

RUN shellCommand
run a shell command on the top of current generated image and commit to the updated image, for example,
RUN echo 'run docker file

ARG varnameWithoutDefaultValue
ARG varname=varDefaultValue
Set a variable that can be set by docker build, those variables are only used during docker build, not available when running the container.
Those variable can be set or replaced when running docker build with --build-arg flag
Example - the below code logs the variable DIST_DIR
FROM nginx:1.18
ARG DIST_DIR=dist
RUN echo 'DIST_DIR is set to' ${DIST_DIR}

ENV envname=envValue
ENV envName the environment value
Set environment variable for docker run, those variables are used during docker build and docker run. The ENV value can be replaced using docker run --env flag.
ENV USER_ID=12345 C
RUN echo 'User ID is set to' ${USER_ID}

ENTRYPOINT ["ExeBashName"]
CMD ["p1", "p2"]
Specify the executable name by EntryPoint, as well as default parameters by CMD. Unlike, Run, CMD does not execute anything at build time, but specifies the intended command when starting the image.



Nginx docker image notes
The default docker config file for nginx is in etc/nginx/config.d/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

 
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }

Command and Argument for docker

CMD
CMD ["nginx"] // example in nginx docker file
CMD ["bash"] //example in ubuntu docker file
specify the command that will run when docker container starts, CMD configuration can be overridden by docker run parameter
docker run ubuntu sleep 5
sleep 5 will replace default "bash" command specified in docker file. You can also create a new docker file to replace the CMD

CMD["sleep", "5"] //the first element in the array is command to execute. This command can be overridden by
docker run newubuntuimage sleep 10

If you only want to specify the command to execute, and leave argument to be overridden, then use ENTRYPOINT in docker file
ENTRYPOINT ["sleep"]
in that case, any docker run command line argument will be appended after the entrypoint command
docker run ubuntu-sleeper 100
will run sleep 100 in docker image

so, CMD will be replaced by command line argument, while ENTRYPOINT will be appended with command line argument.

When both CMD and ENTRYPOINT are used, then entrypoint command is the execution command, and the default argument is what specified in
CMD, CMD value can be overridden by the docker run's argument if provided.
ENTRYPOINT ["sleep"]
CMD ["5"]







Sunday, June 21, 2020

Understanding Azure system assigned and user assigned identity

For most Azure resource, like, Web App, Function App, Azure VM, etc,  there is an identity property under settings section, this identity property allows developer to set a system assigned identity or user assigned identity to an azure resource (i.e secret consumer). The identity can be used by other azure resources (secret holder) to assign permissions to the secret consumers represented by the identity. Usually the secret consume is an .net core web app or a java spring web app, and secret holder is a azure keyvault, which holds the password or connection strings to database or azure storage account.

When system assigned identity is enabled, azure creates an principal ID to represent this azure resource (secret consume), each azure resource can only have one system assigned identity. Other azure resources (secret holder) can set access policies to the principal id of system assigned identity, similar to assigning permission to a role based user account, so that it enables the system assigned identity to send requests to the secure holder resources. 

For example, in Azure keyVault, under Settings' Access Policies blade, if permission is assigned to the system identity for a VM or web app, the assigned permission will show under the current access policy list. 

A typical use case involves a SPA client app, a java spring or .net core web app, datavault, and database or Azure storage account. The SPA app runs on client side and does not hold any sensitive data. The SPA app sends request to java spring or .net web app, which as a system assigned identity to allow it to access the datavault through an access policy defined in datavault, so that the web app itself does not need to save any sensitive data in its code. Also no direct trust relationships need to be configured between web app and database or storage account. The only trust relationship that needs to be configured is between the web app and datavault.

As each azure service can only has one system assigned identity, it may not be enough. In that case, multiple user assigned identities can be created. Those user assigned identities can be associated with any azure resources, and then they can be used in the same way as system assigned identity for assigning permission from other azure resources.

The system assigned identity can be enabled from azure portal or az portal shell. The below command creates a system identity for a azure web app 

az webapp identity assign --name yourWebappName --resource-group yourResourceGroupName

The output includes the principal identity as below

{

  "principalId": "1c36874b-3a68-47b5-88ed-4b1ef9ee45b7",

  "tenantId": "7fe7fa7d-cac4-43e5-8f35-eec8db5a662f",

  "type": "SystemAssigned",

  "userAssignedIdentities": null

}

In order to get permission to azure resource (like a key vault), you can set the permission from the azure resource (secret holder) as below

az keyvault set-policy --name haiquankeyvault --object-id 1c36874b-3a68-47b5-88ed-4b1ef9ee45b7 --secret-permissions get list

After the command, the access policy of the keyvault resource will include a new item for allowed permission assigned to the web app.


One particular use case for Azure managed identity is for Azure virtual machine, as once the permission is assigned to the VM's system identity, then any apps or services running on this Virtual machine can transparently get this VM identity's permission to access the assigned resources without providing any credentials or access key information.

The below command creates a system assigned identity for an Azure virtual machine.

az vm identity assign --name myVM --resource-group myResourceGroup

The output of the command is the identity of the vm as below:

{

  "systemAssignedIdentity": "d5b1bb44-a5b5-4eeb-9c68-920a385d310c",

  "userAssignedIdentities": {}

}

The identity created by the above commands can represent the VM to assign permission to this VM identity. The below code assigning Azure keyvault permission to the system assigned identity created before.

az keyvault set-policy --name haiquankeyvault --object-id d5b1bb44-a5b5-4eeb-9c68-920a385d310c --secret-permissions backup, delete, get, list, purge, recover, restore, set

The below code shows how a .net core apps running in the VM can use SecretClient and DefaultAzureCredential instance s to get the access to the keyvault without providing any credentials.

        static void Main(string[] args)
        {
            string secretName = "mySecret";
            string keyVaultName = "haiquankeyvault";

            var kvUri = "https://haiquankeyvault.vault.azure.net";

            var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());

            Console.Write("Input the value of your secret > ");
            string secretValue = Console.ReadLine();

            client.SetSecret(secretName, secretValue);

            Console.WriteLine(" done.");
}