K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目

  • 1.前言
  • 2.简介
    • 2.1.为什么写这篇文章
    • 2.2.需求描述
    • 2.3.需求分析
  • 3. 部署实战
    • 3.1 环境准备
    • 3.2 image准备
    • 3.3 部署2个实例
      • 3.3.1 编写yaml文件
      • 3.3.2 启动
      • 3.3.3 引入Ingress
        • 3.3.3.1 Ingress简介
        • 3.3.3.2 Ingress 安装
        • 3.3.3.3 Ingress 配置启动
        • 3.3.3.4 验证
        • 3.3.3.4 自动扩缩
  • 4. 总结

1.前言

云原生可以说是当下互联网行业最火爆的概念和技术,云原生从字面意思上来看可以分成云和原生两个部分。
是和本地相对的,传统的应用必须跑在本地服务器上,现在流行的应用都跑在云端,云包含了IaaS,、PaaS和SaaS。
原生就是土生土长的意思,我们在开始设计应用的时候就考虑到应用将来是运行云环境里面的,要充分利用云资源的优点,比如️云服务的弹性和分布式优势。
聊到云原生,避不开的就是容器技术,而docker作为最流行的容器技术,已经经过很多年的线上实战。今天我们不深入聊云原生,docker这些技术概念,今天我们聊一聊时下最火的容器编排技术:K8S-实战部署SpringBoot项目。

2.简介

2.1.为什么写这篇文章

前言中提到云原生dockerK8S,我是18年第一次docker,也是在18年接触K8S,相对这门技术来说,我接触的时候已经有些晚了,因为在之后的面试中,已经感受到这些技术在大厂已经用的很成熟了,之前都在小公司,并不了解这些技术是什么,干什么用,加上国内这方面的资料又比较少,学起来是相当吃力。而到大厂之后,发现这些技术无处不在,并且基础设施建设已经很完备,一键部署云端的骚操作,让开发只需要关心业务而无需关心安装部署等繁琐的工作。祸兮福之所倚;福兮祸之所伏,大厂的技术设施完备的同时,另一方面也消弱了你去了解基础设施背后的技术原理能力。正是认识到这一点,今天才写这篇文章,为迷途中的孩子找到回家的路。废话不多,撸起袖子,干就完了!

这里没有任何马后炮套话,只有粗暴的干货。写大家看得懂、用得着、赚得到的文章是唯一宗旨!

2.2.需求描述

我有一个简单的Springboot项目,想部署在K8S集群中,能够实现扩缩容,负载均衡,同时我有一个互联网域名,我想把这个域名绑定在这个服务上,能够在有网络的地方访问。

2.3.需求分析

这个需求我想在很多刚开始接触docker,k8s等技术的老铁身上都会遇到过,真正实现起来,并不是那么容易,听我一一道来:

  1. image—Springboot项目一般是以jar包的形式跑在像centos等服务器上,运行nohup java -jar xxx.jar &命令就能启动起来。但是在k8s中,运行起来的的并不是jar,而是image,因此我们需要把jar打包成image;
  2. 自动扩缩—最基础的image有了,接下来就要考虑的是自动扩缩:顾名思义,比如说就是在服务访问量大的时候,我可以添加实例,来减少每个服务实例的压力,在访问量小的时候,我可以删除一部分实例,来实现资源的高效利用。
  3. 负载均衡—当我们的实例越来越多,我并不希望把所有的请求都落在一个实例上,如若不然,我们自动扩缩也就没了意义,传统方法我们可以用Nginx等实现负载均衡,待会来看看K8S能做些什么
  4. 域名绑定—这个就没什么好说的了。

3. 部署实战

3.1 环境准备

工欲善其事,必先利其器:

  1. Springboot jar包
  2. K8S集群环境

K8S集群环境部署我就不在这里展开讲了,我们准备一个最简单的Springboot项目,里面只有一个接口,访问localhost:8088,返回服务器的hostname,当整个部署工作完成之后,我们通过域名访问这个接口,返回的应该是不同的containerhostname,那我们的任务就完成了。

 @GetMapping("/")public String sayHello() throws UnknownHostException {String hostname = "Unknown";InetAddress address = InetAddress.getLocalHost();hostname = address.getHostName();return hostname;}

3.2 image准备

我们都知道,所有image的生成都离不开Dockerfile技术,我们有了一个jar包,要利用Dockerfile技术生成一个image。废话不多,上代码:

#使用jdk8作为基础镜像
FROM java:8
#指定作者
MAINTAINER ***
#暴漏容器的8088端口
#EXPOSE 8088
#将复制指定的docker-demo-0.0.1-SNAPSHOT.jar为容器中的job.jar,相当于拷贝到容器中取了个别名
ADD docker-demo-0.0.1-SNAPSHOT.jar /job.jar
#创建一个新的容器并在新的容器中运行命令
RUN bash -c 'touch /job.jar'
#设置时区
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
#相当于在容器中用cmd命令执行jar包  指定外部配置文件
ENTRYPOINT ["java","-jar","/job.jar"]

Dockerfile文件里面有注释,具体的每一行代码什么意思我就不展开多讲了,这不是今天的重点,接下来,我们把docker-demo-0.0.1-SNAPSHOT.jarDockerfile文件放在同一个目录,上传到K8S的master 节点上,在目录内执行如下命令生成images

$ docker build .

我们可以看到生成image的过程,通过docker images 查看镜像

生成一个 docker-demo:latest的image镜像。
注意:我们部署的是集群,要想K8S集群中都能拉到这个镜像,那我们有以下两种方式:

  1. 方法一:我们把这个docker-demo:latest上传到远端仓库,这个仓库可以是我们自己的,或者是像我一样注册一个阿里云的账号,上传到阿里云自己的容器镜像服务仓库,如下图:

    具体步骤
    1.1 docker登陆阿里云容器镜像服务,需要输入密码
$ docker login --username=24k不怕(写自己的用户名) registry.cn-hangzhou.aliyuncs.com

1.2 在阿里云上创建命名空间:例:cuixhao-docker-demo

1.3 镜像打标签

$ docker tag docker-demo:latest registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest

1.4 push到阿里云

$ docker push registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest

1.5 删除掉docker-demo:latest

$ docker rmi docker-demo:latest
  1. 方法二 把刚才创建image的过程,在集群中每一台节点上都执行一遍,保证集群中每一台都有这个镜像。我采用的是二者的结合:先在master上把镜像生成,上传到阿里云,然后在另外的节点上,通过docker pull registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest 命令,从阿里云上拉到本地,然后在通过 docer tag registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest docker-demo:latest命令打标签,然后删掉拉取到的镜像:docker rmi registry.cn-hangzhou.aliyuncs.com/cuixhao-docker-demo/docker-demo:latest,
    为什么这么做因为我阿里云建的命名空间中的image都是私有,K8S拉取image的时候是需要集群中都配置ca证书的,如果设置为公开则不存在这个问题。所以我用docker-demo:latest这个镜像,直接用本地的,部署的时候不用再去阿里云拉取。

3.3 部署2个实例

3.3.1 编写yaml文件

基础镜像准备好了,那我们就开始部署吧。我们知道,k8s有deployment ,service等概念,这里不详细讲,简单描述一下:deployment,管理pod集群,service,管理pod中的服务。我们在master 节点编辑一个 ingress-docker-docker-deployment.yaml 文件

$ vi ingress-docker-docker-deployment.yaml

键入以下内容

apiVersion: apps/v1
kind: Deployment
metadata:name: ingress-docker-demo-deploymentlabels:app: ingress-docker-demo
spec:replicas: 2selector:matchLabels:app: ingress-docker-demotemplate:metadata:labels:app: ingress-docker-demospec:containers:- name: docker-demoimage: docker-demoimagePullPolicy: Neverports:- containerPort: 8088
---
apiVersion: v1
kind: Service
metadata:name: ingress-docker-demo-service
spec:ports:- port: 80protocol: TCPtargetPort: 8088selector:app: ingress-docker-demo

由yaml文件内容我们可以读出:我们创建了一个 名字我为ingress-docker-demo-deployment的deployment,里面有2个 docker-demo 的pod (replicas: 2),和一个名字为ingress-docker-demo-service的service,管理名字为ingress-docker-demo的pod服务,目标端口8088,即我们的springboot服务所用端口,对外提供80端口。

3.3.2 启动

在master执行如下命令启动deployment 和service:

$ kubectl apply -f ingress-docker-docker-deployment.yaml

查看启动结果:

$ kubectl get pod -o wide


我们可以看到启动结果,两个container分别在 worker01,worker02这两个节点上,可以看到,K8S集群给我们分配了两个IP:192.168.14.11,192.168.221.73。我们思考以下三个问题:
a. 在集群中,我们通过以上访问这两个服务,能访问通吗,是通过8088端口还是80端口?
我们不妨尝试一下,在集群中任何一个节点执行如下命令:

$ curl 192.168.14.11:8088
$ curl 192.168.221.73:8088


我们可以看到,接口返回了各自container的hostname,说明我们的服务是部署启动成功了,访问80端口是不通的,有兴趣的老铁可以试一下,因为80是我们对外的端口,所以用container ip是访问不通的。
b. 在集群内部访问,我们如何做到负载均衡?
有老铁可能会考虑一个问题,K8S集群中的pod有可能销毁或者重启,每次重启之后的ip不能保证一致,那以上访问方式肯定是不可采用的。想法很对,我们想访问一个固定的地址,不管pod如何重启,ip如何变化,我只访问这一个ip,这岂不美哉?那我们能不能做到呢?且看如下骚操作:

$ kubectl get svc


通过以上命令,我们找到了ingress-docker-docker-deployment.yaml中定义的名字为 ingress-docker-demo-service的service,它有一个 CLUSTER-IP,PORT为80,那我们根据K8S中的service的作用,做一个大胆的猜测:我们是不是可以固定的通过 10.103.19.71 (省略默认80端口)或者 10.103.19.71:80 来永久访问这两个服务呢?

$ curl 10.103.19.71
$ curl 10.103.19.71:80


答案是肯定的!,它给我们做了负载均衡!

c. 在集群外部我们如何访问这两个服务并且负载均衡?
集群内访问服务,负载均衡都已经做好了。有的老铁会问:集群外服想访问集群内的服务,该如何做呢?别急,还没完!

3.3.3 引入Ingress

3.3.3.1 Ingress简介

我们传统的集群负载均衡做法是在一台机器上安装Nginx,把我们的服务配置在Nginx上,外部直接访问你Nginx,它帮我们做负载,做限流,但是今天我们玩了K8S,就不能在用这种方法了,我们大胆的想一下,我们把所有的东西都在K8S做了,岂不美哉!想法很好,"好事者"已经替我们想到了,并且替我们做到了。
kubernetes ingress 文档
我来简单介绍一下:

如图:在K8S中,Ingress 提供 controller接口,由各个负载均衡厂家实现,传统Nginx是配置在nginx.conf 中,在K8S中,我们只需要配置Ingress 资源yaml就可以,听起来是不是方便多了,我们可以像管理deployment,servicepod一样管理Ingress

3.3.3.2 Ingress 安装

我们使用 Nginx Ingress Controller 来一波骚操作:
编写 ingress-nginx.yaml

$ vi  ingress-nginx.yaml

键入以下内容:

apiVersion: v1
kind: Namespace
metadata:name: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx---kind: ConfigMap
apiVersion: v1
metadata:name: nginx-configurationnamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx---
kind: ConfigMap
apiVersion: v1
metadata:name: tcp-servicesnamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx---
kind: ConfigMap
apiVersion: v1
metadata:name: udp-servicesnamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx---
apiVersion: v1
kind: ServiceAccount
metadata:name: nginx-ingress-serviceaccountnamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:name: nginx-ingress-clusterrolelabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx
rules:- apiGroups:- ""resources:- configmaps- endpoints- nodes- pods- secretsverbs:- list- watch- apiGroups:- ""resources:- nodesverbs:- get- apiGroups:- ""resources:- servicesverbs:- get- list- watch- apiGroups:- ""resources:- eventsverbs:- create- patch- apiGroups:- "extensions"- "networking.k8s.io"resources:- ingressesverbs:- get- list- watch- apiGroups:- "extensions"- "networking.k8s.io"resources:- ingresses/statusverbs:- update---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:name: nginx-ingress-rolenamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx
rules:- apiGroups:- ""resources:- configmaps- pods- secrets- namespacesverbs:- get- apiGroups:- ""resources:- configmapsresourceNames:# Defaults to "<election-id>-<ingress-class>"# Here: "<ingress-controller-leader>-<nginx>"# This has to be adapted if you change either parameter# when launching the nginx-ingress-controller.- "ingress-controller-leader-nginx"verbs:- get- update- apiGroups:- ""resources:- configmapsverbs:- create- apiGroups:- ""resources:- endpointsverbs:- get---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:name: nginx-ingress-role-nisa-bindingnamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx
roleRef:apiGroup: rbac.authorization.k8s.iokind: Rolename: nginx-ingress-role
subjects:- kind: ServiceAccountname: nginx-ingress-serviceaccountnamespace: ingress-nginx---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:name: nginx-ingress-clusterrole-nisa-bindinglabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx
roleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: nginx-ingress-clusterrole
subjects:- kind: ServiceAccountname: nginx-ingress-serviceaccountnamespace: ingress-nginx---apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-ingress-controllernamespace: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginx
spec:replicas: 1selector:matchLabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginxtemplate:metadata:labels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/part-of: ingress-nginxannotations:prometheus.io/port: "10254"prometheus.io/scrape: "true"spec:# wait up to five minutes for the drain of connectionsterminationGracePeriodSeconds: 300serviceAccountName: nginx-ingress-serviceaccounthostNetwork: truenodeSelector:name: ingresskubernetes.io/os: linuxcontainers:- name: nginx-ingress-controllerimage: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1args:- /nginx-ingress-controller- --configmap=$(POD_NAMESPACE)/nginx-configuration- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services- --udp-services-configmap=$(POD_NAMESPACE)/udp-services- --publish-service=$(POD_NAMESPACE)/ingress-nginx- --annotations-prefix=nginx.ingress.kubernetes.iosecurityContext:allowPrivilegeEscalation: truecapabilities:drop:- ALLadd:- NET_BIND_SERVICE# www-data -> 33runAsUser: 33env:- name: POD_NAMEvalueFrom:fieldRef:fieldPath: metadata.name- name: POD_NAMESPACEvalueFrom:fieldRef:fieldPath: metadata.namespaceports:- name: httpcontainerPort: 80- name: httpscontainerPort: 443livenessProbe:failureThreshold: 3httpGet:path: /healthzport: 10254scheme: HTTPinitialDelaySeconds: 10periodSeconds: 10successThreshold: 1timeoutSeconds: 10readinessProbe:failureThreshold: 3httpGet:path: /healthzport: 10254scheme: HTTPperiodSeconds: 10successThreshold: 1timeoutSeconds: 10lifecycle:preStop:exec:command:- /wait-shutdown---

这个文件并不是我胡编乱造自己写的,是"好事者"帮我们做好了,我只是稍作修改:设置网络模式为hostNetwork:true,我希望我在集群中一台机器上开一个80端口,用这台机器作为负载均衡入口,因此:nodeSelector: name: ingress。这是节点选择器配置参数,设置这个,ingress服务会在节点名字为ingress的机器上部署。

接下来我们在集群中的除master节点之外的一个机器上执行下个命令:给这台hostname为worker01-kubeadm-k8s的机器取个别名ingress

$ kubectl label node worker01-kubeadm-k8s name=ingress

接下来,我们在master节点执行安装ingress操作

$ kubectl apply -f ingress-nginx.yaml

安装过程有点儿慢,因为有个镜像比较难拉取:quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1,建议执行 docker pull 先拉取到本地,上传到阿里云,然后在各个节点从阿里云拉取,然后在打tag的骚操作,都是有经验的程序员,你们知道我在说什么!

3.3.3.3 Ingress 配置启动

编写Ingress yaml资源:

$ vi nginx-ingress.yaml

键入以下内容:

#ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: nginx-ingress
spec:rules:- host: test.test.comhttp:paths:- path: /backend:serviceName: ingress-docker-demo-serviceservicePort: 80

文件定义了一种Ingress的资源,配置host为:test.test.com,代理K8S中名字为ingress-docker-demo-service的 Service, Service端口为80.看起来是不是和Nginx配置有点儿类似?
最后一步:启动

$ kubectl apply -f nginx-ingress.yaml

3.3.3.4 验证

因为test.test.com是不存在的域名,我们都是有经验的开发人员,很自然的想到去修改本地host : 添加 ip test.test.com,ip为K8S节点中设置的别名为ingress的ip地址
浏览器访问:


完美!

3.3.3.4 自动扩缩

使用如下命令,可以然服务实现自动扩缩:

$ kubectl autoscale ingress-docker-docker-deployment.yaml --min=2 --max=5 --cpu-percent=80

还有很多自动扩缩的规则,老铁们自己探讨!

4. 总结

K8S实战还有很多玩法,我今天只是讲了最简单的服务部署,在不同的生产环境中,需求也是不一样的,比如说:一个简单的web应用,有mysql数据库,有redis,有springboot应用,都要在K8S中实践,这又是另一种部署方法,但万变不离其宗核心都是要深入了解K8S网络,只有网络打通了,各个组件才会畅通无阻的运行。有兴趣的老铁可以关注一波,一起Hello World!

下一篇:K8S实战进阶篇:一文带你深入了解K8S持久化存储解决方案

K8S实战基础篇:一文带你深入了解K8S实战部署SpringBoot项目相关推荐

  1. Nginx实战基础篇六 通过源码包编译安装部署LNMP搭建Discuz论坛

    Nginx实战基础篇六 通过源码包编译安装部署LNMP搭建Discuz论坛 版权声明: 本文遵循"署名非商业性使用相同方式共享 2.5 中国大陆"协议 您可以自由复制.发行.展览. ...

  2. Nginx实战基础篇一 源码包编译安装部署web服务器

    Nginx实战基础篇一 源码包编译安装部署web服务器 版权声明: 本文遵循"署名非商业性使用相同方式共享 2.5 中国大陆"协议 您可以自由复制.发行.展览.表演.放映.广播或通 ...

  3. Centos7云服务器部署SpringBoot项目(手动配置环境篇)

    文章目录 前言 一.部署Tomcat服务器 1.安装JDK1.8 2.安装与启动tomcat 配置安全组(8080端口) 二.安装JDK8 三.Mysql5.7安装 1.下载mysql 2.启动MyS ...

  4. SpringBoot2零基础到项目实战-基础篇

    springboot2零基础到项目实战-基础篇 课程内容说明 课程单元 学习目标 基础篇 能够创建SpringBoot工程 基于SpringBoot实现ssm/ssmp整合 应用篇 能够掌握Sprin ...

  5. ASP.NET Google Maps Javascript API V3 实战基础篇一检测用户位置

    ASP.NET Google Maps Javascript API V3 实战基础篇一检测用户位置 对于一些基本的东西,google maps JavaScript api v3 文档已经讲解得足够 ...

  6. Xamarin.Forms开发实战基础篇大学霸内部资料

    Xamarin.Forms开发实战基础篇大学霸内部资料 介绍:本教程是国内第一本Xamarin.Forms开发专向教程.本教程针对Xamarin.Forms初学用户,全面细致的讲解Xmarin.For ...

  7. ASP.NET Google Maps Javascript API V3 实战基础篇一获取和设置事件处理程序中的属性...

    ASP.NET Google Maps Javascript API V3 实战基础篇一获取和设置事件处理程序中的属性 <%@ Page Language="C#" Auto ...

  8. Lua与c++交互实战基础篇-夏曹俊-专题视频课程

    Lua与c++交互实战基础篇-10018人已学习 课程介绍         本课程从实战角度讲解了流行的高性能脚本Lua与c++的联合开发,这套方案已经被大量的对性能由要求的系统使用,成为了高性能脚本 ...

  9. k8s集群部署springboot项目

    一.前言 本篇,我们将基于k8s集群,模拟一个比较接近实际业务的使用场景,使用k8s集群部署一个springboot的项目,我们的需求是: 部署SpringBoot项目到阿里云服务器 : 基于容器打包 ...

最新文章

  1. IPSec ××× 在企业网中的应用
  2. 利用string 字符串拷贝
  3. poj 1860 Currency Exchange (最短路bellman_ford思想找正权环 最长路)
  4. ASP.NET CORE WEBAPI文件下载
  5. 【数据库原理及应用】经典题库附答案(14章全)——第九章:数据库安全性
  6. 什么是指利用计算机和现代,现代计算机一般指什么计算机?
  7. linux 多线程客户端服务端通信,[转载]多线程实现服务器和客户端、客户端和客户端通信;需要代码,留言...
  8. Prometheus is an open source monitoring
  9. 暴力破解之NTscan+密码字典工具
  10. 基于6U VPX架构的6槽标准VPX机箱
  11. android 手机 打印 图片,Mopria打印PDF、TXT文档或图片(适用于Android安卓系统)
  12. python中element什么意思_什么是Python中等效的’nth_element’函数?
  13. 黑帽SEO强势技术大纲
  14. 性能测试实战--计划测试(一)
  15. Python爬虫+颜值打分,5000+图片找到你的Mrs. Right
  16. 薛定谔 | 诱导契合对接(结合位点柔性)
  17. 优秀案例 | 长江鲲鹏中地数码:打造智慧城市“数字底座”
  18. vue 更改 element-ui 中 el-table 默认的暂无数据样式
  19. poj 3904 求四元互质集合
  20. windows系统开机自动进行NTP时间同步和系统时间修正

热门文章

  1. 【文献阅读】VAQF: Fully Automatic Software-Hardware Co-Design Framework for Low-Bit Vision Transformer
  2. Substance的置换效果
  3. 16MnDR是什么材料
  4. 基于SSM实现的求职招聘系统【附源码】(毕设)
  5. mysql数据库实验3查询_mysql数据库(3)-查询
  6. alexa是什么_Alexa的简要模式是什么?如何打开(或关闭)它?
  7. 365天挑战LeetCode1000题——Day 117 矩形区域不超过 K 的最大数值和
  8. 循环经济与可持续发展企业——章节测试1
  9. android swf webview,android webview播放swf文件
  10. Windows Store apps开发[8]处理Fullscreen, Snapped和Filled状态