后容器时代技术制高点:API管理平台3Scale的架构设计与部署[转]
前言
时至今日,相信大多数人已经相信容器可以承载生产的应用了,就像当年大多数人相信vSphere虚拟化承载生产的过程一样;历史总是在重演,IT技术的更新总是很快,而作为IT技术人员,顺应趋势是非常重要的。我们当然可以固守已有的知识领域,并以此为生计,但这并不妨碍我们学习新技术、学习开源。
容器带动了PaaS、带动了微服务、带动了Devops的落地和发展。而在容器技术已经成熟的今天,新的技术制高点是如何用容器实现API经济的落地。
API的本质是一种服务,无所不在的服务。移动其实是一个载体、一个表现形式;移动在本质上是让服务变得随时随地可以用。手机上的各种APP,其实都是一个服务的入口和访问口,如何来提供这种服务呢?就是后端跟API相关,安全的去使用API。
今天,我们看一下作为全生命周期API管理魔力象限中,领导者之一的3Scale的架构设计、部署方式、应用集成、报表展现。
接下来,我们看一下API的全生命周期管理的整个迭代,它分为五个阶段:
定义:确定为业务层提供价值的API服务
开发:设计,编码,测试,文档和标准化模板
发布:使用策略和控件安全运行
管理和支持:为协作提供社区论坛和文档
退休:生命结束 - 使用版本控制最佳实践取消发布,与市场进行交流和移除
一、3scale的部署方式
3Scale的两块组件:API网关和API Manager。他们的作用如下:
流量管理(API网关)
1.流量管理在APIcast网关中进行部署
(1)接口处理从外部客户端到后端API服务的API请求
(2)可以处理访问控制、速率限制、安全过滤、日志记录、路由和缓存
2.以异步方式与Red Hat 3scale API Management进行交互:
(1)即使AMP无法访问,API请求也会继续执行
3.使用开源组件实现(默认使用Nginx):
(1).OpenResty和Lua脚本语言
(2)基于Nginx
策略管理(API Manager): 3scale API Management Platform (AMP)
3scale API管理提供了5个关键功能,它们是:
- 访问控制和安全
- API合同和费率限制
- 分析和报告
- 开发人员门户和交互式API文档
- API帐单和付款
API网关虽然大量在互联网中被广泛使用,在企业内部也有用武之地。例如:
用例:内部企业API
1.企业数据中心内部部署允许管理内部API:
(1)安全地管理访问并捕获跨部门的API资产分析
(2)通过以可编程格式提供内部数据和功能,鼓励部门间的协作
2.在大型和不断发展的组织中实现更高的敏捷性,创新性和灵活性
(1) 在开发人员门户中标准化文档
所以说,AP管理是客户PaaS建设的重要一环。
3scale的部署架构。
3scale的部署方式有3种选择:
只将API管理平台(管理部分)运行在Openshift容器云平台上。API网关部署在Openshift外部:
API管理平台全部运行在Openshift容器云平台上,但客户应用部署到Openshift外部:
容器化后的客户应用 和 API管理平台全部运行在Openshift容器云平台上:
其中,第三种方式是我们推荐的。
我们说,一个好汉三个帮。3Scale作为一款API管理平台,我们看一下他的“五大金刚“:
五大金刚之一:在API管理中,业务逻辑的处理,如action chain,并没有放到API网关上,而是由JBoss Fuse提供。具体有:
- Exposes back-end business services:
- Inner (fine-grained)
- Outer (coarse-grained/composite)
- SOAP/REST transformations
- HTTP body introspection/modifications
五大金刚之二:如果客户的API管理需要和mobile app(移动app,如手机、平板上的应用),可以引入Red Hat Mobile Application Platform(社区项目是:FEEDHENRY),具体而言:
- 开发大量的移动应用程序
- 为这些移动应用开发大量支持API
- 提供移动应用程序和支持API的集中管理
五大金刚之三:在API管理系统的单点登录管理方面,引入的是Red Hat Single Sign-On(社区项目是:keycloak),具体而言:
Delegation, AuthN, and AuthZ for:
- APIs
- API developer portal
- API admin portal
Federation with other identity management systems
Implementing OAuth2, OpenId Connect, JSON Web Token
Role-based access control for back-end services
五大金刚之四:而API管理平台的整体运维管理,由红帽Ansible实现,具体而言:
- Provisioning OpenShift Container Platform
- Provisioning APIcast, AMP, Red Hat Single Sign-On, JBoss Fuse, and Jenkins
- Version control
- Ansible Playbooks and tasks can be version controlled
五大金刚之五:API管理平台的CI/CD,由Openshift中容器化的Jenkins实现,具体而言:
- Building/compiling APIs
- Executing API unit tests
- Deploying APIs to development, QA, production
- Creating resources (app plan, services, etc.) in 3scale AMP
- Executing integration tests in staging environment
- Publishing service to production
正是这五大金刚,是3Scale平台羽翼更加丰满,作战能力更强。
二、3scale环境的部署
本次实验环境,首先在笔记本上部署virtualbox虚拟机,用户客户端操作;然后将整套3Scale部署到Openshift中:。
在正式部署之前,我们先看一下3scale的整套部署架构都有什么。
整套3scale将会以pod方式部署到一个项目中。
3scale将会包含api gateway组件、系统组件、后端组件:
接下来我们看一下这些组件的主要作用:
- backend-redis主要作用是存放 API requests and access tokens,他支持 API限速能力和分析历史API请求能力
- system-redis是支持resque和sidekiq作业队列的数据存储缓存; system-mysql是API Manager用于帐户,用户,用户名,密码,电子邮件地址,API定义,设置等信息的数据库。 system-memcache:用于提提高API Manager Web应用程序的性能 backend-listener:实现服务管理API(SM API)的功能; SM API:由API网关,插件或直接API调用来授权和报告API请求,例如:该请求是否经过授权并在速率限制内? backend-listener尝试以尽可能低的延迟进行响应; 通过将任务排入作业队列,将开销更大任务offload到后端worker。 依赖于backend-redis服务。
- system-sidekiq/system-resque:推迟执行一些任务到后台以加快Web响应速度。这两个服务依赖于:system-mysql,system-redis,backend-listener
- system-sphinx:指定存储在system-mysql中的数据以方便管理员门户中的文本搜索功能,他依赖于:system-mysql
- backend-worker:执行从后端监听器卸载的后台任务(入队作业)运行这些排队的作业(保留在后端 - redis中),主要与以前的流量报告有关;取决于:backend-redis服务
- backend-cron:作为cron调度程序将执行失败的jos重新排队,依赖于:backend-redis
接下来,我们看实验环境:
位于virtualbox中的虚拟机(用于模拟应用客户端和OCP的client):
将部署3Scale的Openshift环境:
首先,在github上pull下来两个微服务,用于后续的实验:
第一个是vert.x,第二个是wildfly swarm
然后,在virtualbox虚拟机中安装上OC client,确保可以正常login到openshift:
为RESTful business service applications创建项目:
基于之前pull下来的源码,部署Wildfly Swarm应用:
为service创建一个边界路由:
oc create route edge wfswarmdatestamproute --service=wfswarm-date-service
确保部署好的应用可以被访问:
curl -v -k `echo "https://"$(oc get route/wfswarmdatestamproute -o template --template {{.spec.host}})"/time/now"`
接下来,部署vert.x应用:
同样,为service创建边界路由:
确保部署的应用可以被访问:
curl -v -k `echo "https://"$(oc get route/vertxgreetingroute -o template --template {{.spec.host}})"/hello"`
登录到Openshift,可以看到通过cli部署的两个应用:
在上面的实验中,源码的编译是由openshift完成的。
下面,我们看一下如何在本地编译源码并运行。
切换到源码地址目录:
使用maven进行编译。
编译的时候,会调用当前目录下的pom.xml内容以及$HOME/.m2/repository下的Maven repository:
编译成功:
编译成功以后,被被编译的两个微服务可以在本地启动:
接下来,我们在本地测试编译和部署成功的服务:
接下来,我们在Openshift上部署AMP,使用一个部署AMP的playbook。由于内容太长,我只将service部分贴出来:
apiVersion: v1
kind: Service
metadata:
annotations:
service.alpha.openshift.io/dependencies: '[{"name": "zync-database", "kind": "Service"}]'
labels:
app: zync
name: zync
spec:
ports:
- name: 8080-tcp
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: zync
deploymentconfig: zync
- kind: Service
apiVersion: v1
metadata:
name: "zync-database"
spec:
ports:
- name: postgresql
protocol: TCP
port: 5432
targetPort: 5432
nodePort: 0
selector:
name: "zync-database"
- kind: DeploymentConfig
apiVersion: v1
metadata:
name: zync-database
spec:
paused: true
strategy:
type: Recreate
triggers:
- type: ImageChange
imageChangeParams:
automatic: true
containerNames:
- postgresql
from:
kind: ImageStreamTag
name: postgresql:9.5
- type: ConfigChange
replicas: 1
selector:
name: "zync-database"
template:
metadata:
labels:
name: "zync-database"
spec:
containers:
- name: postgresql
image: " "
ports:
- containerPort: 5432
protocol: TCP
readinessProbe:
timeoutSeconds: 1
initialDelaySeconds: 5
exec:
command:
- "/bin/sh"
- "-i"
- "-c"
- psql -h 127.0.0.1 -U zync -q -d zync_production -c 'SELECT 1'
livenessProbe:
timeoutSeconds: 1
initialDelaySeconds: 30
tcpSocket:
port: 5432
env:
- name: POSTGRESQL_USER
value: zync
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: zync
key: ZYNC_DATABASE_PASSWORD
- name: POSTGRESQL_DATABASE
value: "zync_production"
resources:
limits:
memory: "2G"
volumeMounts:
- name: "zync-database-data"
mountPath: "/var/lib/pgsql/data"
imagePullPolicy: Always
resources:
limits:
cpu: '300m'
memory: 500Mi
requests:
cpu: '300m'
memory: 500Mi
volumes:
- name: "zync-database-data"
emptyDir:
medium: ''
restartPolicy: Always
parameters:
- name: ZYNC_DATABASE_PASSWORD
displayName: PostgreSQL Connection Password
description: Password for the PostgreSQL connection user.
generate: expression
from: "[a-zA-Z0-9]{16}"
required: true
- name: ZYNC_SECRET_KEY_BASE
generate: expression
from: "[a-zA-Z0-9]{16}"
required: true
- name: ZYNC_AUTHENTICATION_TOKEN
generate: expression
from: "[a-zA-Z0-9]{16}"
required: true
- name: ADMIN_PASSWORD
required: true
generate: expression
from: "[a-z0-9]{8}"
- name: ADMIN_USERNAME
value: admin
required: true
- name: APICAST_ACCESS_TOKEN
required: true
generate: expression
from: "[a-z0-9]{8}"
description: "Read Only Access Token that is APIcast going to use to download its configuration."
- name: ADMIN_ACCESS_TOKEN
required: false
generate: expression
from: "[a-z0-9]{16}"
description: "Admin Access Token with all scopes and write permissions for API access."
- name: WILDCARD_DOMAIN
description: Root domain for the wildcard routes. Eg. example.com will generate 3scale-admin.example.com.
required: true
- name: WILDCARD_POLICY
description: Use "Subdomain" to create a wildcard route for apicast wildcard router
required: true
value: "None"
- name: TENANT_NAME
description: "Tenant name under the root that Admin UI will be available with -admin suffix."
required: true
value: "3scale"
- name: MYSQL_USER
displayName: MySQL User
description: Username for MySQL user that will be used for accessing the database.
value: "mysql"
required: true
- name: MYSQL_PASSWORD
displayName: MySQL Password
description: Password for the MySQL user.
generate: expression
from: "[a-z0-9]{8}"
required: true
- name: MYSQL_DATABASE
displayName: MySQL Database Name
description: Name of the MySQL database accessed.
value: "system"
required: true
- name: MYSQL_ROOT_PASSWORD
displayName: MySQL Root password.
description: Password for Root user.
generate: expression
from: "[a-z0-9]{8}"
required: true
- name: SYSTEM_BACKEND_USERNAME
description: Internal 3scale API username for internal 3scale api auth.
value: "3scale_api_user"
required: true
- name: SYSTEM_BACKEND_PASSWORD
description: Internal 3scale API password for internal 3scale api auth.
generate: expression
from: "[a-z0-9]{8}"
required: true
- name: REDIS_IMAGE
description: Redis image to use
required: true
value: "rhscl/redis-32-rhel7:3.2"
- name: MYSQL_IMAGE
description: Mysql image to use
required: true
value: "rhscl/mysql-56-rhel7:5.6"
- name: SYSTEM_BACKEND_SHARED_SECRET
description: Shared secret to import events from backend to system.
generate: expression
from: "[a-z0-9]{8}"
required: true
- name: SYSTEM_APP_SECRET_KEY_BASE
description: System application secret key base
generate: expression
from: "[a-f0-9]{128}"
required: true
- name: APICAST_MANAGEMENT_API
description: "Scope of the APIcast Management API. Can be disabled, status or debug. At least status required for health checks."
required: false
value: "status"
- name: APICAST_OPENSSL_VERIFY
description: "Turn on/off the OpenSSL peer verification when downloading the configuration. Can be set to true/false."
required: false
value: "false"
- name: APICAST_RESPONSE_CODES
description: "Enable logging response codes in APIcast."
value: "true"
required: false
# JA Bride
- name: BACKEND_REDIS_SERVICE_NAME
description: "Name of Backend Redis Service"
value: "backend-redis"
required: true
创建一个项目,用于部署AMP:
在项目中部署AMP:
模板部署的dc如下:
我们查看部署后的pods,这些pods都是AMP的组件:
确认部署好的mysql可以被登录和访问:
我们查看一下AMP所属的Openshift的docker-registry,可以看到部署AMP过程中push的镜像:
通过浏览器登录部署好的3scale:
三、将两个应用与APIcast gateway集成
接下来,我们做一个实验:
用APIcast网关管理刚部署的两个应用的RESTful端点。
我们可以通过配置基于主机的路由来执行此操作,从远程客户端到单个APIcast网关的入站流量使用不同的URL。 根据这些网址,您的APIcast网关将路由到相应的应用。
我们知道,如果我们在OCP中部署应用,那么给应用创建路由的时候,是将FQDN与service关联起来。
而在有3Scale的平台中,我们创建路由(edge route),是将路由指向APIcast gateway的service。然后,再将具体应用的service,与APIcast gateway进行集成,并暴露API集成的时候,用的是应用service的域名。而这个集成的操作,就叫API集成。API集成以后,后续客户端访问应用,访问的是先经过OCP的haproxy,再经过APIcast gateway,然后到达应用:需要注意的是,在实验环境中,有两个apicast的service:apicast-stage和apicast-prod,模拟负责开发测试和生产两个环境的API网关;
有两个应用,这两个应用也存在于stage和prod两个环境,所以下面的实验是创建四条路由,开发测试中的两个应用指向:apicast-stage。生产中的两个应用指向apicast-prod:
首先,查看APIcast路由:
查看APIcast的SVC:
我们创建一个边界路由, 指向APIcast prod网关,以便我们能够访问Swarm service:
oc create route edge swarm-apicast-prod --service=apicast-production --hostname=swarm-apicast-prod-$OCP_PROJECT_PREFIX.$OCP_WILDCARD_DOMAIN
我们创建一个边界路由, 指向APIcast prod网关,以便我们能够访问Vert.x:
oc create route edge vertx-apicast-prod --service=apicast-production --hostname=vertx-prod-apicast-$OCP_PROJECT_PREFIX.$OCP_WILDCARD_DOMAIN
再为swarm-apicast-stage and vertx-apicast-stage创建两条边界路由到staging APIcast gateway:
oc create route edge swarm-apicast-stage --service=apicast-staging --hostname=swarm-stage-apicast-$OCP_PROJECT_PREFIX.$OCP_WILDCARD_DOMAIN
oc create route edge vertx-apicast-stage --service=apicast-staging --hostname=vertx-stage-apicast-$OCP_PROJECT_PREFIX.$OCP_WILDCARD_DOMAIN
四条路由创建完毕,AMP也部署完成。
接下来,我们在3Scale中创建的两个用户为:swarm_dev和vertx_dev
接下来,创建app plan,并为账户创建应用(指定到创建的app plan,然后将app plan publish):
Wildfly Swarm Service
将API User Key记录下来77a3f6e084bb672c3b60c94d68c1a160
注入到环境变量:
echo "export ONPREM_SWARM_USER_KEY=79c8649186e35e1015dc571ec72bfdb7" >> ~/.bashrc
[email protected] / $ source ~/.bashrc
接下来,做Stage环境的服务集成。
首先,我们需要获取应用的几个参数:
[email protected] ~ $ echo -en "\n\nhttp://wfswarm-date-service.$OCP_PROJECT_PREFIX-bservices.svc.cluster.local:8080\n\n"
http://wfswarm-date-service.david-bservices.svc.cluster.local:8080
Staging Public Base URL
[email protected] ~ $ echo -en "\n`oc get route swarm-apicast-stage -n $OCP_PROJECT_PREFIX-3scale-amp --template "https://{{.spec.host}}"`:443\n\n"
https://swarm-stage-apicast-david.apps.na1.openshift.opentlc.com:443
Production Public Base URL
[email protected] ~ $ echo -en "\n`oc get route swarm-apicast-prod -n $OCP_PROJECT_PREFIX-3scale-amp --template "https://{{.spec.host}}"`:443\n\n"
https://swarm-apicast-prod-david.apps.na1.openshift.opentlc.com:443
在API集成中,输入查询到的信息:
Client调用的时候,get /time/now
然后更新信息,应用和API集成成功:
接下来,将集成成功的应用发布到生产:
发布成功以后,应用的两个版本存在于两个环境:
为了让 APIcast gateway尽快刷新,修改其dc,将APICAST_CONFIGURATION_LOADER环境变量从lazy 修改为boot,否则默认每五分钟刷新一次:
oc edit dc apicast-production
接下来,我们测试从客户端对应用发起请求:
curl -v -k `echo -en "\nhttps://"$(oc get route/swarm-apicast-prod -o template --template {{.spec.host}})"/time/now?user_key=$ONPREM_SWARM_USER_KEY\n"`
我们看到请求会指向到apicast gateway:
最终成功调用API,返回信息(当前时间):
接下来,我们再将Vert.x应用集成到apicast gateway。
创建用户:
定义Vert.x service:
gateway使用nginx:
认证方式使用API key:
创建application plan:
创建应用:
注意生成的api key:f29950b33d4b4c7d1d07a7194b9b7d69
发布app plan:
接下来,对应用做API集成:
Private Base URL
[email protected] ~ $ echo -en "\n\nhttp://vertx-greeting-service.$OCP_PROJECT_PREFIX-bservices.svc.cluster.local:8080\n\n"
http://vertx-greeting-service.david-bservices.svc.cluster.local:8080
Staging Public Base URL
[email protected] ~ $ echo -en "\n`oc get route vertx-apicast-stage -n $OCP_PROJECT_PREFIX-3scale-amp --template "https://{{.spec.host}}"`:443\n\n"
https://vertx-stage-apicast-david.apps.na1.openshift.opentlc.com:443
Production Public Base URL
[email protected] ~ $ echo -en "\n`oc get route vertx-apicast-prod -n $OCP_PROJECT_PREFIX-3scale-amp --template "https://{{.spec.host}}"`:443\n\n"
https://vertx-prod-apicast-david.apps.na1.openshift.opentlc.com:443
成功集成:
将应用发布到生产:
发布后的两个环境中的应用:
至此,两个应用与APIcast Gateway集成完毕!
四、APIcast gateway的变更管理
很多时候,我们需要对APIcast gateway做配置变更。在我们的环境中,APIcast gateway是一个容器化的NGINX。
在基于OCP的3Scale中,修改APIcast Gateway大致有两种方法:
1.扩展APIcast Docker镜像:需要修改Docker文件并进行docker build,这对大多数用例来说都是首选方法。
2.覆盖APIcast容器中的特定文件:如果进行少量更改或需要快速尝试对现有APIcast网关容器进行调整。 它涉及创建一个OpenShift容器平台配置映射资源,其中包含您的自定义APIcast配置文件。 引用您的configmap资源的卷将挂载到该pod并覆盖原始APIcast配置文件。例如将NGINX插件添加到您的APIcast网关;或者在PIcast网关中开发并应用自定义安全策略。
在本实验中,我们做最简单的配置更改,这通过修改APIcast网关的nginx.conf文件实现。
在实验环境中,我们登录apicast容器:
oc rsh apicast-production-3-0p34h
查看apicast
root directory:
ls -R /opt/app-root/src | grep ":$" | sed -e 's/:$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'
查找配置文件:
$ find /opt/app-root/src -name '*.lua' -ls
$ find /opt/app-root/src -name '*.conf' -ls
查看APIcast gateway中的nginx配置文件:
sh-4.2$ cat /opt/app-root/src/conf/nginx.conf
env REDIS_HOST;
env REDIS_PORT;
env REDIS_URL;
env RESOLVER;
env BACKEND_ENDPOINT_OVERRIDE;
env OPENSSL_VERIFY;
include ../main.d/*.conf;
error_log /dev/null emerg;
events {
worker_connections 16192;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_names_hash_bucket_size 128;
log_format time '[$time_local] $host:$server_port $remote_addr:$remote_port "$request" $status $body_bytes_sent ($request_time) $post_action_impact';
access_log off;
lua_package_path ";;${prefix}?.lua;${prefix}src/?.lua";
# Enabling the Lua code cache is strongly encouraged for production use
# Disabling it should only be done for testing and development purposes
lua_code_cache on;
include ../http.d/*.conf;
server {
listen 8090;
server_name _;
include ../conf.d/management.conf;
}
server {
listen 8081;
server_name backend;
include ../conf.d/backend.conf;
}
server {
listen 8081 default_server;
server_name echo _;
include ../conf.d/echo.conf;
}
server {
access_log /dev/stdout time;
listen 8080;
server_name _;
underscores_in_headers on;
include ../http.d/ssl.conf;
include ../apicast.d/*.conf;
include ../conf.d/apicast.conf;
}
include ../sites.d/*.conf;
}
在本实验中,我们给APIcast gateway增加一个图片。
图面位于该目录下:
在OCP中创建一个包含图片的security:
oc create secret generic graphics-secret --from-file=$HOME/lab/3scale_onpremise_implementation_labs/resources/misc
接下来,将security 挂载到APIcast pod中:
oc set volume dc/apicast-production --add --overwrite \
> --name=graphics-volume \
> -m /u02/graphics \
> --type=secret \
> --secret-name=graphics-secret \
> --overwrite
warning: volume "graphics-volume" did not previously exist and was not overriden. A new volume with this name has been created instead.deploymentconfig "apicast-production" updated
将nginx.conf配置文件从容器中同步出来,然后进行修改:
[email protected] ~ $ mkdir -p /tmp/apicast_configs/
[email protected] ~ $ oc rsync apicast-production-4-zjhlj:/opt/app-root/src/conf/nginx.conf /tmp/apicast_configs
$ oc create configmap nginx-map --from-file=nginx.conf=/tmp/apicast_configs/nginx.conf
configmap "nginx-map" created
修改apicast gateway的dc:
确认图片已经被加入到pod中.
首先查看链接:
[email protected] ~ $ echo -en "\n\n`oc get route swarm-apicast-prod --template "https://{{.spec.host}}"`:443/rht_3scale.png\n\n"
https://swarm-apicast-prod-david.apps.na1.openshift.opentlc.com:443/rht_3scale.png
pod重新部署以后,依然有图片文件:
然后通过浏览器访问:
https://swarm-apicast-prod-david.apps.na1.openshift.opentlc.com:443/rht_3scale.png
能够显示图片:
五、API分析报告与计费功能
最后,我们看一下API的分析报告:
在另外的一个实验中:
查看API调用,可以看到GET /vocabularies被调用的次数是8:
接下来,通过Postman工具,模拟客户端对api的访问,发一个get请求:
再查查看,GET /vocabularies
被调用次数增加到9:
再次发起3次请求后:
API调用次数增加到:
我们看一下API分析数据的具体内容:
可以下载详细的分析数据表格:
选择以天方式显示:
以小时为单位显示:
从应用视角查看调用:
给用户配置查看报告的权限:
除此之外,我们还可以在3Scale中启动API计费功能:
(具体设置方法:https://access.redhat.com/documentation/en-us/red_hat_3scale/2.saas/html-single/billing/index)
魏新宇
- "大魏分享"运营者、红帽资深解决方案架构师
- 专注开源云计算、容器及自动化运维在金融行业的推广
- 拥有MBA、ITIL V3、Cobit5、C-STAR、TOGAF9.1(鉴定级)等管理认证。
- 拥有红帽RHCE/RHCA、VMware VCP-DCV、VCP-DT、VCP-Network、VCP-Cloud、AIX、HPUX等技术认证。
原文发布于微信公众号 - 大魏分享(david-share)
原文发表时间:2018-05-14
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
发表于 2018-06-25