奇瑞新建輕量電動車生產線 年產能可達6萬輛

據報導,奇瑞新工廠將投產鋁車身骨架純電動乘用車,年產能可達6萬輛,主要生產S51EV、小螞蟻、AOEV等系列電動車。此外新工廠投產後,將成為奇瑞兌現年產20萬輛新能源車產能目標的重要一環。

奇瑞新電動車生產項目位於蕪湖弋江區高新技術開發區,項目總投資15.6億元人民幣,由奇瑞汽車股份有限公司獨資建設,有望於年內正式投產。

奇瑞汽車股份有限公司總經理助理劉志佳此前表示,奇瑞後續的新能源汽車產品,都是以鋁合金框架(含電池鋁合金框架)和分體式碳纖維於一體的輕量化產品構架平臺。今年10月,奇瑞將推出該輕量化平臺打造的首款車型,車身減重將達到30%,該車型的推出會為減排起到很大作用,同時新車的製造過程對於傳統四大工藝(衝壓、焊裝、塗裝、總裝)也將是很大的挑戰。

據劉志佳介紹,2016年奇瑞將新能源車銷量目標定為3.3萬輛,其中純電車目標為28,000輛,插電式混合動力車目標是5,000輛。與2015年銷量表現相比,其今年銷量將實現133%的增幅。

按照奇瑞此前公佈的消息,奇瑞目前已形成全系列的新能源乘用車研發體系和集成平臺,包括四大新能源整車平臺(小型純電動平臺、中型純電動平臺、插電式混動平臺、電四驅平臺);五大通用子系統(動力電池系統、電驅動系統、整車控制系統、PHEV動力總成、電驅動後橋);七大核心技術(電池管理技術、電池系統設計、整車控制技術、PHEV系統設計、智慧互聯設計、輕量化技術、新能源整車集成)。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

FreeSql v0.11 幾個實用功能說明

FreeSql 開源發布快一年了,立志成為 .Net 平台方便好用的 ORM,倉庫地址:

隨着不斷的迭代更新,越來越穩定,也越來越強大。預計在一周年的時候(2020年1月1日)發布 1.0 正式版本。

金九銀十的日子過去了,在這個銅一般的月份里,鄙人做了幾個重大功能,希望對使用者開發提供更大的便利。

  • 一、Dto 映射查詢
  • 二、IncludeMany 聯級加載
  • 三、Where(a => true) 邏輯表達式解析優化
  • 四、SaveManyToMany 聯級保存多對多集合屬性
  • 五、遷移實體 – 到指定表名
  • 六、MySql 特有功能 On Duplicate Key Update,和 Pgsql upsert
  • 七、ISelect.ToDelete 高級刪除
  • 八、全局過濾器

以下的代碼,先決定義代碼如下 :

IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\db1.db;Max Pool Size=10";)
    .UseAutoSyncStructure(true) //自動同步實體結構到數據庫
    .Build();

public class Blog
{
    public Guid Id { get; set; }
    public string Url { get; set; }
    public int Rating { get; set; }
}

一、Dto 映射查詢

class Dto
{
    public Guid Id { get; set; }
    public string Url { get; set; }
    public int xxx { get; set; }
}

fsql.Select<Blog>().ToList<Dto>();
//SELECT Id, Url FROM Blog

fsql.Select<Blog>().ToList(a => new Dto { xxx = a.Rating} );
//SELECT Id, Url, Rating as xxx FROM Blog
//這樣寫,附加所有映射,再額外映射 xxx

fsql.Select<Blog>().ToList(a => new Blog { Id = a.Id }) 
//這樣寫,只查詢 id

fsql.Select<Blog>().ToList(a => new { a.Id }) 
//這樣寫,只查詢 id,返回匿名對象

映射支持單表/多表,是在查詢數據之前映射(不是先查詢所有字段再到內存映射)

查找規則,查找屬性名,會循環內部對象 _tables(join 查詢後會增長),以 主表優先查,直到查到相同的字段。

如:

A, B, C 都有 id,Dto { id, a1, a2, b1, b2 },A.id 被映射。也可以指定 id = C.id 映射。

友情提醒:在 dto 可以直接映射一個導航屬性

二、IncludeMany 聯級加載

之前已經實現,有設置關係,和未設置關係 的導航集合屬性聯級加載。

有設置關係的(支持一對多、多對多):

fsql.Select<Tag>().IncludeMany(a => a.Goods).ToList();

未設置關係的,臨時指定關係(只支持一對多):

fsql.Select<Goods>().IncludeMany(a => a.Comment.Where(b => b.TagId == a.Id));

只查詢每項子集合的前幾條數據,避免像EfCore加載所有數據導致IO性能低下(比如某商品下有2000條評論):

fsql.Select<Goods>().IncludeMany(a => a.Comment.Take(10));

上面已有的 IncludeMany 功能還不夠自由靈活。

新功能1:在 Dto 上做映射 IncludeMany

老的 IncludeMany 限制只能在 ISelect 內使用,必須要先查上級數據,解決這個問題我們做了直接在 Dto 上做映射:

查詢 Goods 商品表,分類1、分類2、分類3 各10條數據

//定義臨時類,也可以是 Dto 類
class Dto {
    public int TypeId { get; set; }
    public List<Goods > GoodsList { get; set; }
}

var dto = new [] { 1,2,3 }.Select(a => new Dto { TypeId = a }).ToList();
dto.IncludeMany(d => d.GoodsList.Take(10).Where(gd => gd.TypeId == d.TypeId));

//執行后,dto 每個元素.Vods 將只有 10條記錄

現在 IncludeMany 不再是 ISelect 的專利,普通的 List<T> 也可以用它來貪婪加載數據,並準確填充到內部各元素中。

新功能2:查詢子集合表的指定字段

老的 IncludeMany 限制只能查子表的所有字段,子表過段多過的話比較浪費 IO 性能。

新功能可以設置子集合返回部分字段,避免子集合字段過多的問題。

fsql.Select<Tag>().IncludeMany(a => a.Goods.Select(b => new Goods { Id = b.Id, Title = b.Title }));
//只查詢 goods 表 id, title 字段,再作填充

三、Where(a => true) 邏輯表達式解析優化

相信很多 ORM 解析表達式的時候處理不了這個問題,我們之前已經解決了 99%。

這個月發現還有一餘孽未清,發現問題后及時解決了,並增加單元測試代碼以絕後患。

四、SaveManyToMany 聯級保存多對多集合屬性

在此之前,FreeSql.DbContext 和 倉儲實現,已經實現了聯級保存功能,如下:

聯級保存功能可實現保存對象的時候,將其【OneToMany】、【ManyToMany】導航屬性集合也一併保存。

全局關閉:

fsql.SetDbContextOptions(opt => opt.EnableAddOrUpdateNavigateList = false);

局部關閉:

var repo = fsql.GetRepository<T>();
repo.DbContextOptions.EnableAddOrUpdateNavigateList = false;

新功能:

保存實體的指定【多對多】導航屬性,SaveManyToMany 方法實現在 BaseRepository、DbContext。

解決問題:當實體類導航數據過於複雜的時候,選擇關閉聯級保存的功能是明智之選,但是此時【多對多】數據保存功能寫起來非常繁瑣麻煩(因為要與現有數據對比后保存)。

var song = new Song { Id = 1 };
song.Tags = new List<Tag>();
song.Tags.Add(new Tag ...);
song.Tags.Add(new Tag ...);
song.Tags.Add(new Tag ...);
repo.SaveManyToMany(song, "Tags");
//輕鬆保存 song 與 tag 表的關聯

機制規則與聯級保存的【多對多】一樣,如下:

我們對中間表的保存是完整對比操作,對外部實體的操作只作新增(注意不會更新)

  • 屬性集合為空時,刪除他們的所有關聯數據(中間表)
  • 屬性集合不為空時,與數據庫存在的關聯數據(中間表)完全對比,計算出應該刪除和添加的記錄

五、遷移實體 – 到指定表名

fsql.CodeFirst.SyncStructure(typeof(Log), "Log_1"); //遷移到 Log_1 表
fsql.CodeFirst.SyncStructure(typeof(Log), "Log_2"); //遷移到 Log_2 表

在此功能上,我們對分表功能做了點升級,以下動作都會做遷移動作:

fsql.Select<Log>().AsTable((_, oldname) => $"{oldname}_1");
fsql.GetRepository<Log>(null, oldname => $"{oldname}_1");

六、MySql 特有功能 On Duplicate Key Update,和 Pgsql upsert

FreeSql 提供了多種插入或更新方法,v0.11 之前主要使用 FreeSql.Repository/FreeSql.DbContext 庫提供的方法實現。

FreeSql.Repository 之 InsertOrUpdate

此方法與 FreeSql.DbContext AddOrUpdate 方法功能一樣。

var repo = fsql.GetRepository<T>();
repo.InsertOrUpdate(實體);

如果內部的狀態管理存在數據,則更新。

如果內部的狀態管理不存在數據,同查詢數據庫,是否存在。

存在則更新,不存在則插入

缺點:不支持批量操作

新功能:MySql 特有功能 On Duplicate Key Update

FreeSql.Provider.MySql 和 FreeSql.Provider.MySqlConnector 在 v0.11.11 版本已支持 MySql 特有的功能,On Duplicate Key Update。

這個功能也可以實現插入或更新數據,並且支持批量操作。

class TestOnDuplicateKeyUpdateInfo
{
    [Column(IsIdentity = true)]
    public int id { get; set; }
    public string title { get; set; }
    public DateTime time { get; set; }
}

var item = new TestOnDuplicateKeyUpdateInfo { id = 100, title = "title-100", time = DateTime.Parse("2000-01-01") };
fsql.Insert(item)
    .NoneParameter()
    .OnDuplicateKeyUpdate().ToSql();
//INSERT INTO `TestOnDuplicateKeyUpdateInfo`(`id`, `title`, `time`) VALUES(100, 'title-100', '2000-01-01 00:00:00.000')
//ON DUPLICATE KEY UPDATE
//`title` = VALUES(`title`), 
//`time` = VALUES(`time`)

OnDuplicateKeyUpdate() 之後可以調用的方法:

方法名 描述
IgnoreColumns 忽略更新的列,機制和 IUpdate.IgnoreColumns 一樣
UpdateColumns 指定更新的列,機制和 IUpdate.UpdateColumns 一樣
Set 手工指定更新的列,與 IUpdate.Set 功能一樣
SetRaw 作為 Set 方法的補充,可傳入 SQL 字符串
ToSql 返回即將執行的 SQL 語句
ExecuteAffrows 執行,返回影響的行數

IInsert 與 OnDuplicateKeyUpdate 都有 IgnoreColumns、UpdateColumns 方法。

當插入實體/集合實體的時候,忽略了 time 列,代碼如下:

fsql.Insert(item)
    .IgnoreColumns(a => a.time)
    .NoneParameter()
    .OnDuplicateKeyUpdate().ToSql();
//INSERT INTO `TestOnDuplicateKeyUpdateInfo`(`id`, `title`) VALUES(200, 'title-200')
//ON DUPLICATE KEY UPDATE
//`title` = VALUES(`title`), 
//`time` = '2000-01-01 00:00:00.000'

我們發現,UPDATE time 部分變成了常量,而不是 VALUES(`time`),機制如下:

當 insert 部分中存在的列,在 update 中將以 VALUES(`字段`) 的形式設置;

當 insert 部分中不存在的列,在 update 中將為常量形式設置,當操作實體數組的時候,此常量為 case when … end 執行(與 IUpdate 一樣);

新功能2:PostgreSQL 特有功能 On Conflict Do Update

使用方法 MySql OnDuplicateKeyUpdate 大致相同。

七、ISelect.ToDelete 高級刪除

默認 IDelete 不支持導航對象,多表關聯等。ISelect.ToDelete 可將查詢轉為刪除對象,以便支持導航對象或其他查詢功能刪除數據,如下:

fsql.Select<T1>().Where(a => a.Options.xxx == 1).ToDelete().ExecuteAffrows();

注意:此方法不是將數據查詢到內存循環刪除,上面的代碼產生如下 SQL 執行:

DELETE FROM `T1` WHERE id in (select a.id from T1 a left join Options b on b.t1id = a.id where b.xxx = 1)

複雜刪除使用該方案的好處:

  • 刪除前可預覽測試數據,防止錯誤刪除操作;
  • 支持更加複雜的刪除操作(IDelete 默認只支持簡單的操作),甚至在 ISelect 上使用 Limit(10) 將只刪除附合條件的前 10條記錄;

還有 ISelect.ToUpdate 高級更新數據功能,使用方法類似

八、全局過濾器

FreeSql 基礎層實現了 Select/Update/Delete 可設置的全局過濾器功能。

public static AsyncLocal<Guid> TenantId { get; set; } = new AsyncLocal<Guid>();

fsql.GlobalFilter
    .Apply<TestAddEnum>("test1", a => a.Id == TenantId.Value)
    .Apply<AuthorTest>("test2", a => a.Id == 111)
    .Apply<AuthorTest>("test3", a => a.Name == "11");

Apply 泛型參數可以設置為任何類型,當使用 Select/Update/Delete 方法時會進行過濾器匹配嘗試(try catch):

  • 匹配成功的,將附加 where 條件;
  • 匹配失敗的,標記下次不再匹配,避免性能損耗;

如何禁用?

fsql.Select<TestAddEnum>().ToList(); //所有生效
fsql.Select<TestAddEnum>().DisableGlobalFilter("test1").ToList(); //禁用 test1
fsql.Select<TestAddEnum>().DisableGlobalFilter().ToList(); //禁用所有

fsql.Update/Delete 方法效果同上。

注意:IFreeSql.GlobalFilter 與 倉儲過濾器 不是一個功能,可以同時生效

鳴謝

感謝反饋 bug 的朋友!

倉庫地址:

請移步更新日誌:https://github.com/2881099/FreeSql/wiki/%e6%9b%b4%e6%96%b0%e6%97%a5%e5%bf%97

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

Docker學習(五)-Kubernetes 集群搭建 – Spring Boot 應用,Docker學習-Docker搭建Consul集群,Docker學習-簡單的私有DockerHub搭建,Docker學習-Spring Boot on Docker,Docker學習-Kubernetes – 集群部署,Docker學習-Kubernetes – Spring Boot 應用,Docker學習-Kubernetes – Spring Boot 應用

 

Docker學習

 

 

簡介

kubernetes,簡稱K8s,是用8代替8個字符“ubernete”而成的縮寫。是一個開源的,用於管理雲平台中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器化的應用簡單並且高效(powerful),Kubernetes提供了應用部署,規劃,更新,維護的一種機制。

Kubernetes是Google開源的一個容器編排引擎,它支持自動化部署、大規模可伸縮、應用容器化管理。在生產環境中部署一個應用程序時,通常要部署該應用的多個實例以便對應用請求進行負載均衡。 在Kubernetes中,我們可以創建多個容器,每個容器裏面運行一個應用實例,然後通過內置的負載均衡策略,實現對這一組應用實例的管理、發現、訪問,而這些細節都不需要運維人員去進行複雜的手工配置和處理。  

基本概念

Kubernetes 中的絕大部分概念都抽象成 Kubernetes 管理的一種資源對象

  • Master:Master 節點是 Kubernetes 集群的控制節點,負責整個集群的管理和控制。Master 節點上包含以下組件:
  • kube-apiserver:集群控制的入口,提供 HTTP REST 服務
  • kube-controller-manager:Kubernetes 集群中所有資源對象的自動化控制中心
  • kube-scheduler:負責 Pod 的調度
  • Node:Node 節點是 Kubernetes 集群中的工作節點,Node 上的工作負載由 Master 節點分配,工作負載主要是運行容器應用。Node 節點上包含以下組件:

    • kubelet:負責 Pod 的創建、啟動、監控、重啟、銷毀等工作,同時與 Master 節點協作,實現集群管理的基本功能。
    • kube-proxy:實現 Kubernetes Service 的通信和負載均衡
    • 運行容器化(Pod)應用
  • Pod: Pod 是 Kubernetes 最基本的部署調度單元。每個 Pod 可以由一個或多個業務容器和一個根容器(Pause 容器)組成。一個 Pod 表示某個應用的一個實例

  • ReplicaSet:是 Pod 副本的抽象,用於解決 Pod 的擴容和伸縮
  • Deployment:Deployment 表示部署,在內部使用ReplicaSet 來實現。可以通過 Deployment 來生成相應的 ReplicaSet 完成 Pod 副本的創建
  • Service:Service 是 Kubernetes 最重要的資源對象。Kubernetes 中的 Service 對象可以對應微服務架構中的微服務。Service 定義了服務的訪問入口,服務的調用者通過這個地址訪問 Service 後端的 Pod 副本實例。Service 通過 Label Selector 同後端的 Pod 副本建立關係,Deployment 保證後端Pod 副本的數量,也就是保證服務的伸縮性。

Kubernetes 主要由以下幾個核心組件組成:

  • etcd 保存了整個集群的狀態,就是一個數據庫;
  • apiserver 提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API 註冊和發現等機制;
  • controller manager 負責維護集群的狀態,比如故障檢測、自動擴展、滾動更新等;
  • scheduler 負責資源的調度,按照預定的調度策略將 Pod 調度到相應的機器上;
  • kubelet 負責維護容器的生命周期,同時也負責 Volume(CSI)和網絡(CNI)的管理;
  • Container runtime 負責鏡像管理以及 Pod 和容器的真正運行(CRI);
  • kube-proxy 負責為 Service 提供 cluster 內部的服務發現和負載均衡;

當然了除了上面的這些核心組件,還有一些推薦的插件:

  • kube-dns 負責為整個集群提供 DNS 服務
  • Ingress Controller 為服務提供外網入口
  • Heapster 提供資源監控
  • Dashboard 提供 GUI

組件通信

Kubernetes 多組件之間的通信原理:

  • apiserver 負責 etcd 存儲的所有操作,且只有 apiserver 才直接操作 etcd 集群
  • apiserver 對內(集群中的其他組件)和對外(用戶)提供統一的 REST API,其他組件均通過 apiserver 進行通信

    • controller manager、scheduler、kube-proxy 和 kubelet 等均通過 apiserver watch API 監測資源變化情況,並對資源作相應的操作
    • 所有需要更新資源狀態的操作均通過 apiserver 的 REST API 進行
  • apiserver 也會直接調用 kubelet API(如 logs, exec, attach 等),默認不校驗 kubelet 證書,但可以通過 --kubelet-certificate-authority 開啟(而 GKE 通過 SSH 隧道保護它們之間的通信)

比如最典型的創建 Pod 的流程:​​

  • 用戶通過 REST API 創建一個 Pod
  • apiserver 將其寫入 etcd
  • scheduluer 檢測到未綁定 Node 的 Pod,開始調度並更新 Pod 的 Node 綁定
  • kubelet 檢測到有新的 Pod 調度過來,通過 container runtime 運行該 Pod
  • kubelet 通過 container runtime 取到 Pod 狀態,並更新到 apiserver 中

 

集群部署

 

使用kubeadm工具安裝

1. master和node 都用yum 安裝kubelet,kubeadm,docker
2. master 上初始化:kubeadm init
3. master 上啟動一個flannel的pod
4. node上加入集群:kubeadm join

 

準備環境

Centos7  192.168.50.21 k8s-master  
Centos7  192.168.50.22 k8s-node01
Centos7  192.168.50.23 k8s-node02

修改主機名(3台機器都需要修改)

hostnamectl set-hostname k8s-master
hostnamectl set-hostname k8s-node01
hostnamectl set-hostname k8s-node02

關閉防火牆

systemctl stop firewalld.service

配置docker yum源

yum install -y yum-utils device-mapper-persistent-data lvm2 wget
cd /etc/yum.repos.d
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

配置kubernetes yum 源

cd /opt/
wget https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
wget https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
rpm --import yum-key.gpg
rpm --import rpm-package-key.gpg
cd /etc/yum.repos.d
vi kubernetes.repo 
輸入以下內容
[kubernetes]
name=Kubernetes Repo
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
enabled=1

yum repolist

master和node 安裝kubelet,kubeadm,docker

yum install docker 
yum install kubelet-1.13.1
yum install kubeadm-1.13.1

master 上安裝kubectl

yum install kubectl-1.13.1

docker的配置

配置私有倉庫和鏡像加速地址,私有倉庫配置參見 

vi /etc/docker/daemon.json

 

{
    "registry-mirror":[
        "http://hub-mirror.c.163.com"
    ],
    "insecure-registries":[
        "192.168.50.24:5000"
    ]
}

 

啟動docker

systemctl daemon-reload
systemctl start docker 
docker info

master 上初始化:kubeadm init 

vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false"
kubeadm init \
    --apiserver-advertise-address=192.168.50.21 \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.13.1 \
    --pod-network-cidr=10.244.0.0/16

初始化命令說明:

--apiserver-advertise-address

指明用 Master 的哪個 interface 與 Cluster 的其他節點通信。如果 Master 有多個 interface,建議明確指定,如果不指定,kubeadm 會自動選擇有默認網關的 interface。

--pod-network-cidr

指定 Pod 網絡的範圍。Kubernetes 支持多種網絡方案,而且不同網絡方案對 –pod-network-cidr 有自己的要求,這裏設置為 10.244.0.0/16 是因為我們將使用 flannel 網絡方案,必須設置成這個 CIDR。

--image-repository

Kubenetes默認Registries地址是 k8s.gcr.io,在國內並不能訪問 gcr.io,在1.13版本中我們可以增加–image-repository參數,默認值是 k8s.gcr.io,將其指定為阿里雲鏡像地址:registry.aliyuncs.com/google_containers。

--kubernetes-version=v1.13.1 

關閉版本探測,因為它的默認值是stable-1,會導致從https://dl.k8s.io/release/stable-1.txt下載最新的版本號,我們可以將其指定為固定版本(最新版:v1.13.1)來跳過網絡請求。

初始化過程中

[preflight] You can also perform this action in beforehand using 'kubeadm config images pull' 是在下載鏡像文件,過程比較慢。
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 24.002300 seconds 這個過程也比較慢 可以忽略
 
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.50.21]
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-master localhost] and IPs [192.168.50.21 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-master localhost] and IPs [192.168.50.21 127.0.0.1 ::1]
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 24.002300 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-master" as an annotation
[mark-control-plane] Marking the node k8s-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node k8s-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: 7ax0k4.nxpjjifrqnbrpojv
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 192.168.50.21:6443 --token 7ax0k4.nxpjjifrqnbrpojv --discovery-token-ca-cert-hash sha256:95942f10859a71879c316e75498de02a8b627725c37dee33f74cd040e1cd9d6b

初始化過程說明:

1) [preflight] kubeadm 執行初始化前的檢查。
2) [kubelet-start] 生成kubelet的配置文件”/var/lib/kubelet/config.yaml”
3) [certificates] 生成相關的各種token和證書
4) [kubeconfig] 生成 KubeConfig 文件,kubelet 需要這個文件與 Master 通信
5) [control-plane] 安裝 Master 組件,會從指定的 Registry 下載組件的 Docker 鏡像。
6) [bootstraptoken] 生成token記錄下來,後邊使用kubeadm join往集群中添加節點時會用到
7) [addons] 安裝附加組件 kube-proxy 和 kube-dns。
8) Kubernetes Master 初始化成功,提示如何配置常規用戶使用kubectl訪問集群。
9) 提示如何安裝 Pod 網絡。
10) 提示如何註冊其他節點到 Cluster。

 

異常情況:

 [WARNING Service-Docker]: docker service is not enabled, please run 'systemctl enable docker.service'
        [WARNING Swap]: running with swap on is not supported. Please disable swap
        [WARNING Hostname]: hostname "k8s-master" could not be reached
        [WARNING Hostname]: hostname "k8s-master": lookup k8s-master on 114.114.114.114:53: no such host
        [WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'

運行

systemctl enable docker.service
systemctl enable kubelet.service

會提示以下錯誤

  [WARNING Hostname]: hostname "k8s-master" could not be reached
        [WARNING Hostname]: hostname "k8s-master": lookup k8s-master on 114.114.114.114:53: no such host
error execution phase preflight: [preflight] Some fatal errors occurred:

配置host

cat >> /etc/hosts << EOF
192.168.50.21 k8s-master
192.168.50.22 k8s-node01
192.168.50.23 k8s-node02
EOF

再次運行初始化命令會出現

  [ERROR NumCPU]: the number of available CPUs 1 is less than the required 2   --設置虛擬機CPU個數大於2
        [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

設置好虛擬機CPU個數,重啟后再次運行:

kubeadm init \
    --apiserver-advertise-address=192.168.50.21 \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.13.1 \
    --pod-network-cidr=10.244.0.0/16

 

[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull

解決辦法:docker.io倉庫對google的容器做了鏡像,可以通過下列命令下拉取相關鏡像

先看下需要用到哪些

kubeadm config images list

配置yum源

[root@k8s-master opt]# vi kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: v1.13.1
imageRepository: registry.aliyuncs.com/google_containers
apiServer:
  certSANs:
  - 192.168.50.21
controlPlaneEndpoint: "192.168.50.20:16443"
networking:
  # This CIDR is a Calico default. Substitute or remove for your CNI provider.
  podSubnet: "172.168.0.0/16"
 kubeadm config images pull --config /opt/kubeadm-config.yaml

初始化master

kubeadm init --config=kubeadm-config.yaml  --upload-certs

 

xecution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR FileAvailable--etc-kubernetes-manifests-kube-apiserver.yaml]: /etc/kubernetes/manifests/kube-apiserver.yaml already exists
        [ERROR FileAvailable--etc-kubernetes-manifests-kube-controller-manager.yaml]: /etc/kubernetes/manifests/kube-controller-manager.yaml already exists
        [ERROR FileAvailable--etc-kubernetes-manifests-kube-scheduler.yaml]: /etc/kubernetes/manifests/kube-scheduler.yaml already exists
        [ERROR FileAvailable--etc-kubernetes-manifests-etcd.yaml]: /etc/kubernetes/manifests/etcd.yaml already exists
        [ERROR Port-10250]: Port 10250 is in use

kubeadm會自動檢查當前環境是否有上次命令執行的“殘留”。如果有,必須清理后再行執行init。我們可以通過”kubeadm reset”來清理環境,以備重來。

[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[kubelet-check] Initial timeout of 40s passed.

==原因==

這是因為kubelet沒啟動

==解決==

systemctl restart kubelet

如果啟動不了kubelet

kubelet.service - kubelet: The Kubernetes Node Agent

 

則可能是swap交換分區還開啟的原因
-關閉swap

swapoff -a

-配置kubelet

vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false"

 再次運行

kubeadm init \
    --apiserver-advertise-address=192.168.50.21 \
    --image-repository registry.aliyuncs.com/google_containers \
    --kubernetes-version v1.13.1 \
    --pod-network-cidr=10.244.0.0/16

 

 

配置 kubectl

kubectl 是管理 Kubernetes Cluster 的命令行工具,前面我們已經在所有的節點安裝了 kubectl。Master 初始化完成后需要做一些配置工作,然後 kubectl 就能使用了。
依照 kubeadm init 輸出的最後提示,推薦用 Linux 普通用戶執行 kubectl。

  • 創建普通用戶centos
#創建普通用戶並設置密碼123456
useradd centos && echo "centos:123456" | chpasswd centos

#追加sudo權限,並配置sudo免密
sed -i '/^root/a\centos  ALL=(ALL)       NOPASSWD:ALL' /etc/sudoers

#保存集群安全配置文件到當前用戶.kube目錄
su - centos
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

#啟用 kubectl 命令自動補全功能(註銷重新登錄生效)
echo "source <(kubectl completion bash)" >> ~/.bashrc

需要這些配置命令的原因是:Kubernetes 集群默認需要加密方式訪問。所以,這幾條命令,就是將剛剛部署生成的 Kubernetes 集群的安全配置文件,保存到當前用戶的.kube 目錄下,kubectl 默認會使用這個目錄下的授權信息訪問 Kubernetes 集群。
如果不這麼做的話,我們每次都需要通過 export KUBECONFIG 環境變量告訴 kubectl 這個安全配置文件的位置。
配置完成后centos用戶就可以使用 kubectl 命令管理集群了。

查看集群狀態:

kubectl get cs

 

 

 

 

 

 部署網絡插件

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

  

kubectl get 重新檢查 Pod 的狀態

 

 

部署worker節點

 在master機器保存生成號的鏡像文件

docker save -o master.tar registry.aliyuncs.com/google_containers/kube-proxy:v1.13.1 registry.aliyuncs.com/google_containers/kube-apiserver:v1.13.1 registry.aliyuncs.com/google_containers/kube-controller-manager:v1.13.1 registry.aliyuncs.com/google_containers/kube-scheduler:v1.13.1  registry.aliyuncs.com/google_containers/coredns:1.2.6  registry.aliyuncs.com/google_containers/etcd:3.2.24 registry.aliyuncs.com/google_containers/pause:3.1

注意對應的版本號

將master上保存的鏡像同步到節點上

scp master.tar node01:/root/
scp master.tar node02:/root/

將鏡像導入本地,node01,node02

 docker load< master.tar

配置host,node01,node02

cat >> /etc/hosts << EOF
192.168.50.21 k8s-master
192.168.50.22 k8s-node01
192.168.50.23 k8s-node02
EOF

配置iptables,node01,node02

echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

-關閉swap,node01,node02

swapoff -a

-配置kubelet,node01,node02

vi /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false" 
systemctl enable docker.service
systemctl enable kubelet.service

啟動docker,node01,node02

service docker strat

部署網絡插件,node01,node02

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

獲取join指令,master

kubeadm token create --print-join-command
kubeadm token create --print-join-command
kubeadm join 192.168.50.21:6443 --token n9g4nq.kf8ppgpgb3biz0n5 --discovery-token-ca-cert-hash sha256:95942f10859a71879c316e75498de02a8b627725c37dee33f74cd040e1cd9d6b

 

在子節點運行指令 ,node01,node02

kubeadm join 192.168.50.21:6443 --token n9g4nq.kf8ppgpgb3biz0n5 --discovery-token-ca-cert-hash sha256:95942f10859a71879c316e75498de02a8b627725c37dee33f74cd040e1cd9d6b
[preflight] Running pre-flight checks
[discovery] Trying to connect to API Server "192.168.50.21:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.50.21:6443"
[discovery] Requesting info from "https://192.168.50.21:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "192.168.50.21:6443"
[discovery] Successfully established connection with API Server "192.168.50.21:6443"
[join] Reading configuration from the cluster...
[join] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] WARNING: unable to stop the kubelet service momentarily: [exit status 4]
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.13" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-node01" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.

在master上查看節點狀態

kubectl get nodes

 

 這種狀態是錯誤的 ,只有一台聯機正確

查看node01,和node02發現 node01有些進程沒有完全啟動

刪除node01所有運行的容器,node01

docker stop $(docker ps -q) & docker rm $(docker ps -aq)

重置 kubeadm ,node01

kubeadm reset

獲取join指令,master

kubeadm token create --print-join-command

再次在node01上運行join

 

 

 

查看node01鏡像運行狀態

  

查看master狀態

  

nodes狀態全部為ready,由於每個節點都需要啟動若干組件,如果node節點的狀態是 NotReady,可以查看所有節點pod狀態,確保所有pod成功拉取到鏡像並處於running狀態:

kubectl get pod --all-namespaces -o wide

  

配置kubernetes UI圖形化界面

創建kubernetes-dashboard.yaml

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# ------------------- Dashboard Secret ------------------- #

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kube-system
type: Opaque

---
# ------------------- Dashboard Service Account ------------------- #

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Role & Role Binding ------------------- #

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
rules:
  # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create"]
  # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create"]
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
  verbs: ["get", "update", "delete"]
  # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["kubernetes-dashboard-settings"]
  verbs: ["get", "update"]
  # Allow Dashboard to get metrics from heapster.
- apiGroups: [""]
  resources: ["services"]
  resourceNames: ["heapster"]
  verbs: ["proxy"]
- apiGroups: [""]
  resources: ["services/proxy"]
  resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard-minimal
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Deployment ------------------- #

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
      - name: kubernetes-dashboard
        image: registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1  
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
        volumeMounts:
        - name: kubernetes-dashboard-certs
          mountPath: /certs
          # Create on-disk volume to store exec logs
        - mountPath: /tmp
          name: tmp-volume
        livenessProbe:
          httpGet:
            scheme: HTTPS
            path: /
            port: 8443
          initialDelaySeconds: 30
          timeoutSeconds: 30
      volumes:
      - name: kubernetes-dashboard-certs
        secret:
          secretName: kubernetes-dashboard-certs
      - name: tmp-volume
        emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule

---
# ------------------- Dashboard Service ------------------- #

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  type: NodePort
  ports:
  - port: 443
    targetPort: 8443
    nodePort: 30000
  selector:
    k8s-app: kubernetes-dashboard

 

執行以下命令創建kubernetes-dashboard:

kubectl create -f kubernetes-dashboard.yaml

如果出現

Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": secrets "kubernetes-dashboard-certs" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": serviceaccounts "kubernetes-dashboard" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": roles.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": rolebindings.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" already exists
Error from server (AlreadyExists): error when creating "kubernetes-dashboard.yaml": deployments.apps "kubernetes-dashboard" already exists

運行delete清理

kubectl delete -f kubernetes-dashboard.yaml

查看組件運行狀態

kubectl get pods --all-namespaces

 

 

 ErrImagePull 拉取鏡像失敗

手動拉取 並重置tag

docker pull registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1
docker tag registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1

重新創建

  

 

ImagePullBackOff

默認情況是會根據配置文件中的鏡像地址去拉取鏡像,如果設置為IfNotPresent 和Never就會使用本地鏡像。

IfNotPresent :如果本地存在鏡像就優先使用本地鏡像。
Never:直接不再去拉取鏡像了,使用本地的;如果本地不存在就報異常了。

 spec:
      containers:
      - name: kubernetes-dashboard
        image: registry.cn-hangzhou.aliyuncs.com/rsqlh/kubernetes-dashboard:v1.10.1  
        imagePullPolicy: IfNotPresent

 

查看映射狀態 

 kubectl get service  -n kube-system

 

 

 

 

創建能夠訪問 Dashboard 的用戶

 新建文件 account.yaml ,內容如下:

# Create Service Account
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
# Create ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')

 

 複製token登陸

 

 

configmaps is forbidden: User "system:serviceaccount:kube-system:admin-user" cannot list resource "configmaps" in API group "" in the namespace "default" 

 授權用戶

kubectl create clusterrolebinding test:admin-user --clusterrole=cluster-admin --serviceaccount=kube-system:admin-user

 

 

 NodePort方式,可以到任意一個節點的XXXX端口查看

 

 

本文參考:

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

go中的數據結構-接口interface

1. 接口的基本使用

  golang中的interface本身也是一種類型,它代表的是一個方法的集合。任何類型只要實現了接口中聲明的所有方法,那麼該類就實現了該接口。與其他語言不同,golang並不需要顯式聲明類型實現了某個接口,而是由編譯器和runtime進行檢查。

聲明

 1 type 接口名 interface{
 2     方法1
 3     方法2
 4     ...
 5    方法n 
 6 }
 7 type 接口名 interface {
 8     已聲明接口名1
 9     ...
10     已聲明接口名n
11 }
12 type iface interface{
13     tab *itab
14     data unsafe.Pointer
15 }

  接口自身也是一種結構類型,只是編譯器對其做了很多限制:

  • 不能有字段
  • 不能定義自己的方法
  • 只能聲明方法,不能實現
  • 可嵌入其他接口類型
 1 package main
 2 
 3     import (
 4         "fmt"
 5     )
 6 
 7     // 定義一個接口
 8     type People interface {
 9         ReturnName() string
10     }
11 
12     // 定義一個結構體
13     type Student struct {
14         Name string
15     }
16 
17     // 定義結構體的一個方法。
18     // 突然發現這個方法同接口People的所有方法(就一個),此時可直接認為結構體Student實現了接口People
19     func (s Student) ReturnName() string {
20         return s.Name
21     }
22 
23     func main() {
24         cbs := Student{Name:"小明"}
25 
26         var a People
27         // 因為Students實現了接口所以直接賦值沒問題
28         // 如果沒實現會報錯:cannot use cbs (type Student) as type People in assignment:Student does not implement People (missing ReturnName method)
29         a = cbs       
30         name := a.ReturnName() 
31         fmt.Println(name) // 輸出"小明"
32     }

  如果一個接口不包含任何方法,那麼它就是一個空接口(empty interface),所有類型都符合empty interface的定義,因此任何類型都能轉換成empty interface。

  接口的值簡單來說,是由兩部分組成的,就是類型和數據,判斷兩個接口是相等,就是看他們的這兩部分是否相等;另外類型和數據都為nil才代表接口是nil。

1 var a interface{} 
2 var b interface{} = (*int)(nil)
3 fmt.Println(a == nil, b == nil) //true false

2. 接口嵌套

  像匿名字段那樣嵌入其他接口。目標類型方法集中必須擁有包含嵌入接口方法在內的全部方法才算實現了該接口。嵌入其他接口類型相當於將其聲明的方法集中導入。這就要求不能有同名方法,不能嵌入自身或循環嵌入。

 1 type stringer interfaceP{
 2      string() string
 3 }
 4 
 5 type tester interface {
 6     stringer
 7     test()
 8 }    
 9 
10 type data struct{}
11 
12 func (*data) test() {}
13 
14 func (data) string () string {
15     return ""
16 }
17 
18 func main() {
19     var d data 
20     var t tester = &d 
21     t.test()
22     println(t.string())
23 }

  超集接口變量可隱式轉換為子集,反過來不行。

3. 接口的實現

golang的接口檢測既有靜態部分,也有動態部分。

  • 靜態部分
    對於具體類型(concrete type,包括自定義類型) -> interface,編譯器生成對應的itab放到ELF的.rodata段,後續要獲取itab時,直接把指針指向存在.rodata的相關偏移地址即可。具體實現可以看golang的提交日誌CL 20901、CL 20902。
    對於interface->具體類型(concrete type,包括自定義類型),編譯器提取相關字段進行比較,並生成值

  • 動態部分
    在runtime中會有一個全局的hash表,記錄了相應type->interface類型轉換的itab,進行轉換時候,先到hash表中查,如果有就返回成功;如果沒有,就檢查這兩種類型能否轉換,能就插入到hash表中返回成功,不能就返回失敗。注意這裏的hash表不是go中的map,而是一個最原始的使用數組的hash表,使用開放地址法來解決衝突。主要是interface <-> interface(接口賦值給接口、接口轉換成另一接口)使用到動態生產itab。

interface的結構如下:

接口類型的結構interfacetype

 1 type interfacetype struct {
 2     typ     _type   
 3     pkgpath name   //記錄定義接口的包名
 4     mhdr    []imethod  //一個imethod切片,記錄接口中定義的那些函數。
 5 }
 6 
 7 // imethod表示接口類型上的方法
 8 type imethod struct {
 9     name nameOff // name of method
10     typ  typeOff // .(*FuncType) underneath
11 }

  nameOff 和 typeOff 類型是 int32 ,這兩個值是鏈接器負責嵌入的,相對於可執行文件的元信息的偏移量。元信息會在運行期,加載到 runtime.moduledata 結構體中。

4. 接口值的結構iface和eface

 為了性能,golang專門分了兩種interface,eface和iface,eface就是空接口,iface就是有方法的接口。

 1 type iface struct { 
 2     tab  *itab
 3     data unsafe.Pointer
 4 }
 5 
 6 type eface struct {
 7     _type *_type
 8     data  unsafe.Pointer
 9 }
10 
11 type itab struct {
12     inter *interfacetype   //inter接口類型
13     _type *_type   //_type數據類型
14     hash  uint32  //_type.hash的副本。用於類型開關。 hash哈希的方法
15     _     [4]byte
16     fun   [1]uintptr  // 大小可變。 fun [0] == 0表示_type未實現inter。 fun函數地址佔位符
17 }

  iface結構體中的data是用來存儲實際數據的,runtime會申請一塊新的內存,把數據考到那,然後data指向這塊新的內存。

itab中的hash方法拷貝自_type.hash;fun是一個大小為1的uintptr數組,當fun[0]為0時,說明_type並沒有實現該接口,當有實現接口時,fun存放了第一個接口方法的地址,其他方法一次往下存放,這裏就簡單用空間換時間,其實方法都在_type字段中能找到,實際在這記錄下,每次調用的時候就不用動態查找了。

4.1 全局的itab table

iface.go:

1 const itabInitSize = 512
2 
3 // 注意:如果更改這些字段,請在itabAdd的mallocgc調用中更改公式。
4 type itabTableType struct {
5     size    uintptr             // 條目數組的長度。始終為2的冪。
6     count   uintptr             // 當前已填寫的條目數。
7     entries [itabInitSize]*itab // really [size] large
8 }

  可以看出這個全局的itabTable是用數組在存儲的,size記錄數組的大小,總是2的次冪。count記錄數組中已使用了多少。entries是一個*itab數組,初始大小是512。

5. 接口類型轉換

  把一個具體的值,賦值給接口,會調用conv系列函數,例如空接口調用convT2E系列、非空接口調用convT2I系列,為了性能考慮,很多特例的convT2I64、convT2Estring諸如此類,避免了typedmemmove的調用。

 1 func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
 2     if raceenabled {
 3         raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
 4     }
 5     if msanenabled {
 6         msanread(elem, t.size)
 7     }
 8     x := mallocgc(t.size, t, true)
 9     // TODO: 我們分配一個清零的對象只是為了用實際數據覆蓋它。
10     //確定如何避免歸零。同樣在下面的convT2Eslice,convT2I,convT2Islice中。
11     typedmemmove(t, x, elem)
12     e._type = t
13     e.data = x
14     return
15 }
16 
17 func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
18     t := tab._type
19     if raceenabled {
20         raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
21     }
22     if msanenabled {
23         msanread(elem, t.size)
24     }
25     x := mallocgc(t.size, t, true)
26     typedmemmove(t, x, elem)
27     i.tab = tab
28     i.data = x
29     return
30 }
31 
32 func convT2I16(tab *itab, val uint16) (i iface) {
33     t := tab._type
34     var x unsafe.Pointer
35     if val == 0 {
36         x = unsafe.Pointer(&zeroVal[0])
37     } else {
38         x = mallocgc(2, t, false)
39         *(*uint16)(x) = val
40     }
41     i.tab = tab
42     i.data = x
43     return
44 }
45 
46 func convI2I(inter *interfacetype, i iface) (r iface) {
47     tab := i.tab
48     if tab == nil {
49         return
50     }
51     if tab.inter == inter {
52         r.tab = tab
53         r.data = i.data
54         return
55     }
56     r.tab = getitab(inter, tab._type, false)
57     r.data = i.data
58     return
59 }

  可以看出:

  • 具體類型轉空接口,_type字段直接複製源的type;mallocgc一個新內存,把值複製過去,data再指向這塊內存。
  • 具體類型轉非空接口,入參tab是編譯器生成的填進去的,接口指向同一個入參tab指向的itab;mallocgc一個新內存,把值複製過去,data再指向這塊內存。
  • 對於接口轉接口,itab是調用getitab函數去獲取的,而不是編譯器傳入的。

對於那些特定類型的值,如果是零值,那麼不會mallocgc一塊新內存,data會指向zeroVal[0]。

5.1 接口轉接口

 1 func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {
 2     tab := i.tab
 3     if tab == nil {
 4         return
 5     }
 6     if tab.inter != inter {
 7         tab = getitab(inter, tab._type, true)
 8         if tab == nil {
 9             return
10         }
11     }
12     r.tab = tab
13     r.data = i.data
14     b = true
15     return
16 }
17 
18 func assertE2I(inter *interfacetype, e eface) (r iface) {
19     t := e._type
20     if t == nil {
21         // 顯式轉換需要非nil接口值。
22         panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
23     }
24     r.tab = getitab(inter, t, false)
25     r.data = e.data
26     return
27 }
28 
29 func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
30     t := e._type
31     if t == nil {
32         return
33     }
34     tab := getitab(inter, t, true)
35     if tab == nil {
36         return
37     }
38     r.tab = tab
39     r.data = e.data
40     b = true
41     return
42 }

  我們看到有兩種用法:

  • 返回值是一個時,不能轉換就panic。
  • 返回值是兩個時,第二個返回值標記能否轉換成功

  此外,data複製的是指針,不會完整拷貝值。每次都malloc一塊內存,那麼性能會很差,因此,對於一些類型,golang的編譯器做了優化。

5.2 接口轉具體類型

  接口判斷是否轉換成具體類型,是編譯器生成好的代碼去做的。我們看個empty interface轉換成具體類型的例子:

 1 var EFace interface{}
 2 var j int
 3 
 4 func F4(i int) int{
 5     EFace = I
 6     j = EFace.(int)
 7     return j
 8 }
 9 
10 func main() {
11     F4(10)
12 }

反彙編:

  go build -gcflags ‘-N -l’ -o tmp build.go

  go tool objdump -s “main.F4” tmp

  可以看彙編代碼:

1 MOVQ main.EFace(SB), CX       //CX = EFace.typ
2 LEAQ type.*+60128(SB), DX    //DX = &type.int
3 CMPQ DX, CX.                         //if DX == AX

  可以看到empty interface轉具體類型,是編譯器生成好對比代碼,比較具體類型和空接口是不是同一個type,而不是調用某個函數在運行時動態對比。

5.3 非空接口類型轉換

 1 var tf Tester
 2 var t testStruct
 3 
 4 func F4() int{
 5     t := tf.(testStruct)
 6     return t.i
 7 }
 8 
 9 func main() {
10     F4()
11 }
12 //反彙編
13 MOVQ main.tf(SB), CX   // CX = tf.tab(.inter.typ)
14 LEAQ go.itab.main.testStruct,main.Tester(SB), DX // DX = <testStruct,Tester>對應的&itab(.inter.typ)
15 CMPQ DX, CX //

  可以看到,非空接口轉具體類型,也是編譯器生成的代碼,比較是不是同一個itab,而不是調用某個函數在運行時動態對比。

6. 獲取itab的流程

  golang interface的核心邏輯就在這,在get的時候,不僅僅會從itabTalbe中查找,還可能會創建插入,itabTable使用容量超過75%還會擴容。看下代碼:

 1 func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
 2     if len(inter.mhdr) == 0 {
 3         throw("internal error - misuse of itab")
 4     }
 5 
 6     // 簡單的情況
 7     if typ.tflag&tflagUncommon == 0 {
 8         if canfail {
 9             return nil
10         }
11         name := inter.typ.nameOff(inter.mhdr[0].name)
12         panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
13     }
14 
15     var m *itab
16 
17     //首先,查看現有表以查看是否可以找到所需的itab。
18     //這是迄今為止最常見的情況,因此請不要使用鎖。
19     //使用atomic確保我們看到該線程完成的所有先前寫入更新itabTable字段(在itabAdd中使用atomic.Storep)。
20     t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
21     if m = t.find(inter, typ); m != nil {
22         goto finish
23     }
24 
25     // 未找到。抓住鎖,然後重試。
26     lock(&itabLock)
27     if m = itabTable.find(inter, typ); m != nil {
28         unlock(&itabLock)
29         goto finish
30     }
31 
32     // 條目尚不存在。進行新輸入並添加。
33     m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
34     m.inter = inter
35     m._type = typ
36     m.init()
37     itabAdd(m)
38     unlock(&itabLock)
39 finish:
40     if m.fun[0] != 0 {
41         return m
42     }
43     if canfail {
44         return nil
45     }
46     //僅當轉換時才會發生,使用ok形式已經完成一次,我們得到了一個緩存的否定結果。
47     //緩存的結果不會記錄,缺少接口函數,因此初始化再次獲取itab,以獲取缺少的函數名稱。
48     panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
49 }

  流程如下:

  • 先用t保存全局itabTable的地址,然後使用t.find去查找,這樣是為了防止查找過程中,itabTable被替換導致查找錯誤。
  • 如果沒找到,那麼就會上鎖,然後使用itabTable.find去查找,這樣是因為在第一步查找的同時,另外一個協程寫入,可能導致實際存在卻查找不到,這時上鎖避免itabTable被替換,然後直接在itaTable中查找。
  • 再沒找到,說明確實沒有,那麼就根據接口類型、數據類型,去生成一個新的itab,然後插入到itabTable中,這裏可能會導致hash表擴容,如果數據類型並沒有實現接口,那麼根據調用方式,該報錯報錯,該panic panic。

  這裏我們可以看到申請新的itab空間時,內存空間的大小是unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize,參照前面接受的結構,len(inter.mhdr)就是接口定義的方法數量,因為字段fun是一個大小為1的數組,所以len(inter.mhdr)-1,在fun字段下面其實隱藏了其他方法接口地址。

6.1 在itabTable中查找itab find

 1 func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
 2     // 編譯器為我們提供了一些很好的哈希碼。
 3     return uintptr(inter.typ.hash ^ typ.hash)
 4 }
 5 
 6    // find在t中找到給定的接口/類型對。
 7    // 如果不存在給定的接口/類型對,則返回nil。
 8 func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
 9     // 使用二次探測實現。
10      //探測順序為h(i)= h0 + i *(i + 1)/ 2 mod 2 ^ k。
11      //我們保證使用此探測序列擊中所有表條目。
12     mask := t.size - 1
13     h := itabHashFunc(inter, typ) & mask
14     for i := uintptr(1); ; i++ {
15         p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
16         // 在這裏使用atomic read,所以如果我們看到m!= nil,我們也會看到m字段的初始化。
17         // m := *p
18         m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
19         if m == nil {
20             return nil
21         }
22         if m.inter == inter && m._type == typ {
23             return m
24         }
25         h += I
26         h &= mask
27     }
28 }

  從註釋可以看到,golang使用的開放地址探測法,用的是公式h(i) = h0 + i*(i+1)/2 mod 2^k,h0是根據接口類型和數據類型的hash字段算出來的。以前的版本是額外使用一個link字段去連到下一個slot,那樣會有額外的存儲,性能也會差寫,在1.11中我們看到有了改進。

6.2 檢查並生成itab init

 1 // init用所有代碼指針填充m.fun數組m.inter / m._type對。 如果該類型未實現該接口,將m.fun [0]設置為0,並返回缺少的接口函數的名稱。
 2 //可以在同一m上多次調用此函數,即使同時調用也可以。
 3 func (m *itab) init() string {
 4     inter := m.inter
 5     typ := m._type
 6     x := typ.uncommon()
 7 
 8     // inter和typ都有按名稱排序的方法,
 9      //並且接口名稱是唯一的,
10      //因此可以在鎖定步驟中對兩者進行迭代;
11      //循環是O(ni + nt)而不是O(ni * nt)。
12     ni := len(inter.mhdr)
13     nt := int(x.mcount)
14     xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
15     j := 0
16 imethods:
17     for k := 0; k < ni; k++ {
18         i := &inter.mhdr[k]
19         itype := inter.typ.typeOff(i.ityp)
20         name := inter.typ.nameOff(i.name)
21         iname := name.name()
22         ipkg := name.pkgPath()
23         if ipkg == "" {
24             ipkg = inter.pkgpath.name()
25         }
26         for ; j < nt; j++ {
27             t := &xmhdr[j]
28             tname := typ.nameOff(t.name)
29             if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
30                 pkgPath := tname.pkgPath()
31                 if pkgPath == "" {
32                     pkgPath = typ.nameOff(x.pkgpath).name()
33                 }
34                 if tname.isExported() || pkgPath == ipkg {
35                     if m != nil {
36                         ifn := typ.textOff(t.ifn)
37                         *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
38                     }
39                     continue imethods
40                 }
41             }
42         }
43         // didn't find method
44         m.fun[0] = 0
45         return iname
46     }
47     m.hash = typ.hash
48     return ""
49 }

  這個方法會檢查interface和type的方法是否匹配,即type有沒有實現interface。假如interface有n中方法,type有m中方法,那麼匹配的時間複雜度是O(n x m),由於interface、type的方法都按字典序排,所以O(n+m)的時間複雜度可以匹配完。在檢測的過程中,匹配上了,依次往fun字段寫入type中對應方法的地址。如果有一個方法沒有匹配上,那麼就設置fun[0]為0,在外層調用會檢查fun[0]==0,即type並沒有實現interface。

  這裏我們還可以看到golang中continue的特殊用法,要直接continue到外層的循環中,那麼就在那一層的循環上加個標籤,然後continue 標籤。

6.3 把itab插入到itabTable中 itabAdd

 1 // itabAdd將給定的itab添加到itab哈希表中。
 2 //必須保持itabLock。
 3 func itabAdd(m *itab) {
 4     // 設置了mallocing時,錯誤可能導致調用此方法,通常是因為這是在恐慌時調用的。
 5     //可靠地崩潰,而不是僅在需要增長時崩潰哈希表。
 6     if getg().m.mallocing != 0 {
 7         throw("malloc deadlock")
 8     }
 9 
10     t := itabTable
11     if t.count >= 3*(t.size/4) { // 75% 負載係數
12         // 增長哈希表。
13         // t2 = new(itabTableType)+一些其他條目我們撒謊並告訴malloc我們想要無指針的內存,因為所有指向的值都不在堆中。
14         t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
15         t2.size = t.size * 2
16 
17         // 複製條目。
18         //注意:在複製時,其他線程可能會尋找itab和找不到它。沒關係,他們將嘗試獲取Itab鎖,因此請等到複製完成。
19         if t2.count != t.count {
20             throw("mismatched count during itab table copy")
21         }
22         // 發布新的哈希表。使用原子寫入:請參閱getitab中的註釋。
23         atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
24         // 採用新表作為我們自己的表。
25         t = itabTable
26         // 注意:舊錶可以在此處進行GC處理。
27     }
28     t.add(m)
29 }
30 // add將給定的itab添加到itab表t中。
31 //必須保持itabLock。
32 func (t *itabTableType) add(m *itab) {
33     //請參閱註釋中的有關探查序列的註釋。
34     //將新的itab插入探針序列的第一個空位。
35     mask := t.size - 1
36     h := itabHashFunc(m.inter, m._type) & mask
37     for i := uintptr(1); ; i++ {
38         p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
39         m2 := *p
40         if m2 == m {
41             //給定的itab可以在多個模塊中使用並且由於全局符號解析的工作方式,
42             //指向itab的代碼可能已經插入了全局“哈希”。
43             return
44         }
45         if m2 == nil {
46             // 在這裏使用原子寫,所以如果讀者看到m,它也會看到正確初始化的m字段。
47             // NoWB正常,因為m不在堆內存中。
48             // *p = m
49             atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
50             t.count++
51             return
52         }
53         h += I
54         h &= mask
55     }
56 }

  可以看到,當hash表使用達到75%或以上時,就會進行擴容,容量是原來的2倍,申請完空間,就會把老表中的數據插入到新的hash表中。然後使itabTable指向新的表,最後把新的itab插入到新表中。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

016.Kubernetes二進制部署所有節點kube-proxy

一 部署 kube-proxy


kube-proxy 運行在所有節點上,它監聽 apiserver 中 service 和 endpoint 的變化情況,創建路由規則以提供服務 IP 和負載均衡功能。

1.1 安裝kube-proxy


提示:k8smaster01節點已下載相應二進制,可直接分發至node節點。

1.2 分發kube-proxy

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     scp kubernetes/server/bin/kube-proxy root@${all_ip}:/opt/k8s/bin/
  7     ssh root@${all_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 創建kube-scheduler證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-proxy-csr.json <<EOF
  3 {
  4   "CN": "system:kube-proxy",
  5   "key": {
  6     "algo": "rsa",
  7     "size": 2048
  8   },
  9   "names": [
 10     {
 11       "C": "CN",
 12       "ST": "Shanghai",
 13       "L": "Shanghai",
 14       "O": "k8s",
 15       "OU": "System"
 16     }
 17   ]
 18 }
 19 EOF
 20 #創建kube-scheduler的CA證書請求文件



解釋:

  • CN:指定該證書的 User 為 system:kube-proxy;
  • 預定義的 RoleBinding system:node-proxier 將User system:kube-proxy 與 Role system:node-proxier 綁定,該 Role 授予了調用 kube-apiserver Proxy 相關 API 的權限;
  • 該證書只會被 kube-proxy 當做 client 證書使用,所以 hosts 字段為空。




  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \
  3 -ca-key=/opt/k8s/work/ca-key.pem -config=/opt/k8s/work/ca-config.json \
  4 -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy		#生成CA密鑰(ca-key.pem)和證書(ca.pem)


1.4 創建和分發kubeconfig


kube-proxy 使用 kubeconfig 文件訪問 apiserver,該文件提供了 apiserver 地址、嵌入的 CA 證書和 kube-proxy 證書:

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# kubectl config set-cluster kubernetes \
  4   --certificate-authority=/opt/k8s/work/ca.pem \
  5   --embed-certs=true \
  6   --server=${KUBE_APISERVER} \
  7   --kubeconfig=kube-proxy.kubeconfig
  8 
  9 [root@k8smaster01 work]# kubectl config set-credentials kube-proxy \
 10   --client-certificate=kube-proxy.pem \
 11   --client-key=kube-proxy-key.pem \
 12   --embed-certs=true \
 13   --kubeconfig=kube-proxy.kubeconfig
 14 
 15 [root@k8smaster01 work]# kubectl config set-context default \
 16   --cluster=kubernetes \
 17   --user=kube-proxy \
 18   --kubeconfig=kube-proxy.kubeconfig
 19 
 20 [root@k8smaster01 work]# kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
 21 
 22 [root@k8smaster01 ~]# cd /opt/k8s/work
 23 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 24 [root@k8smaster01 work]# for node_name in ${NODE_NAMES[@]}
 25   do
 26     echo ">>> ${node_name}"
 27     scp kube-proxy.kubeconfig root@${node_name}:/etc/kubernetes/
 28   done


1.5 創建kube-proxy 配置文件


從 v1.10 開始,kube-proxy 部分參數可以配置文件中配置。可以使用 –write-config-to 選項生成該配置文件。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-proxy-config.yaml.template <<EOF
  3 kind: KubeProxyConfiguration
  4 apiVersion: kubeproxy.config.k8s.io/v1alpha1
  5 clientConnection:
  6   burst: 200
  7   kubeconfig: "/etc/kubernetes/kube-proxy.kubeconfig"
  8   qps: 100
  9 bindAddress: ##ALL_IP##
 10 healthzBindAddress: ##ALL_IP##:10256
 11 metricsBindAddress: ##ALL_IP##:10249
 12 enableProfiling: true
 13 clusterCIDR: ${CLUSTER_CIDR}
 14 hostnameOverride: ##ALL_NAME##
 15 mode: "ipvs"
 16 portRange: ""
 17 kubeProxyIPTablesConfiguration:
 18   masqueradeAll: false
 19 kubeProxyIPVSConfiguration:
 20   scheduler: rr
 21   excludeCIDRs: []
 22 EOF



解釋:

  • bindAddress: 監聽地址;
  • clientConnection.kubeconfig: 連接 apiserver 的 kubeconfig 文件;
  • clusterCIDR: kube-proxy 根據 –cluster-cidr 判斷集群內部和外部流量,指定 –cluster-cidr 或 –masquerade-all 選項后 kube-proxy 才會對訪問 Service IP 的請求做 SNAT;
  • hostnameOverride: 參數值必須與 kubelet 的值一致,否則 kube-proxy 啟動後會找不到該 Node,從而不會創建任何 ipvs 規則;
  • mode: 使用 ipvs 模式。

1.6 分發配置文件

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for (( i=0; i < 6; i++ ))
  4   do
  5     echo ">>> ${ALL_NAMES[i]}"
  6     sed -e "s/##ALL_NAME##/${ALL_NAMES[i]}/" -e "s/##ALL_IP##/${ALL_IPS[i]}/" kube-proxy-config.yaml.template > kube-proxy-config-${ALL_NAMES[i]}.yaml.template
  7     scp kube-proxy-config-${ALL_NAMES[i]}.yaml.template root@${ALL_NAMES[i]}:/etc/kubernetes/kube-proxy-config.yaml
  8   done


1.7 創建kube-proxy的systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > kube-proxy.service <<EOF
  4 [Unit]
  5 Description=Kubernetes Kube-Proxy Server
  6 Documentation=https://github.com/GoogleCloudPlatform/kubernetes
  7 After=network.target
  8 
  9 [Service]
 10 WorkingDirectory=${K8S_DIR}/kube-proxy
 11 ExecStart=/opt/k8s/bin/kube-proxy \\
 12   --config=/etc/kubernetes/kube-proxy-config.yaml \\
 13   --logtostderr=true \\
 14   --v=2
 15 Restart=on-failure
 16 RestartSec=5
 17 LimitNOFILE=65536
 18 
 19 [Install]
 20 WantedBy=multi-user.target
 21 EOF


1.8 分發kube-proxy systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]}
  4   do
  5     echo ">>> ${all_name}"
  6     scp kube-proxy.service root@${all_name}:/etc/systemd/system/
  7   done						#分發system


二 啟動並驗證

2.1 啟動kube-proxy 服務

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "mkdir -p ${K8S_DIR}/kube-proxy"
  7     ssh root@${all_ip} "modprobe ip_vs_rr"
  8     ssh root@${all_ip} "systemctl daemon-reload && systemctl enable kube-proxy && systemctl restart kube-proxy"
  9   done						#啟動服務前必須先創建工作目錄


2.2 檢查kube-proxy 服務

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
  3   do
  4     echo ">>> ${all_ip}"
  5     ssh root@${all_ip} "systemctl status kube-proxy|grep Active"
  6   done


2.3 查看監聽端口


kube-proxy 監聽 10249 和 10256 端口:

  • 10249:對外提供 /metrics;
  • 10256:對外提供 /healthz 的訪問。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "sudo netstat -lnpt|grep kube-prox"
  7   done



2.4 查看ipvs 路由規則

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]}
  4   do
  5     echo ">>> ${all_ip}"
  6     ssh root@${all_ip} "/usr/sbin/ipvsadm -ln"
  7   done



可見所有通過 https 訪問 K8S SVC kubernetes 的請求都轉發到 kube-apiserver 節點的 6443 端口。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

ThreadLocal 源碼解析

在activeJDBC框架內部的實現中看到了 ThreadLocal 這個類,記錄下了每個線程獨有的連接

private static final ThreadLocal<HashMap<String, Connection>> connectionsTL = new ThreadLocal<>();

感覺是個知識點,就打開源碼看看了。先看一下源碼里的解釋

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

這個鳥文,瞎翻譯一下,就是:

這個類提供了供線程專享的變量。這些變量不同與其它普通的變量,它是每個線程都有一個自己的獨立初始化的變量(通過get和set方法實現)。這個類的實例常用於類的私有靜態字段,以實現每個線程都有自己的狀態(例如userId,事務ID等)。

先跑一下用法吧,

package com.test.threadlocal;

public class TestController {
    
    private static int index = 0;
    private static String str = "這個字符串是每個線程共享的";
    // 這個變量,看似是一個類的靜態屬性,實則是每個線程有自己獨有的區域
    private static ThreadLocal<String> threadStr = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "main線程專享";
        }
    };      
    
    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 3; i++) {
            Thread t = new MyThread();
            t.start();
            t.join();
        }
        
        System.out.println(str);
        System.out.println(threadStr.get());
    }
    
    static class MyThread extends Thread{

        @Override
        public void run() {
            index++;
            str = "第" + index + "個str";
            threadStr.set("第" + index + "個threadStr");
        }
        
        
    }

}

這個例子中,從str和threadStr變量的打印結果可以看出來。str被所有的線程讀和寫,threadStr在每個線程內部開闢了一塊線程專享的區域。接下來,我們看一下具體實現。
先看一下構造函數

     /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();
     /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

構造函數是空的,但是,該類有一個私有整型常量threadLocalHashCode。nextHashCode()方法我們就不看了,省的一如源碼深似海。看鳥文的話,大概就是每new一個ThreadLocal變量的時候,就會生成一個散列碼,該碼非極端情況下與某個整數取模后不容易衝突(這句話有點迷吧,其實我也不懂)
然後看一下set方法

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

容易看出,這個方法設置每個線程自己的value,相當於當前線程是key,然後得出一個ThreadLocalMap。顯然,這個map用來保存線程內部的值,既然是map當然每個線程可以保存多個數值了,該map的value我們猜一下就是我要保存的具體的值,估計是用Object類聲明的。那key是什麼呢?我們看下ThreadLocalMap類的構造方法。

/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
                
/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

我的天!這個類沒有繼承我們想象中的HashMap,或者是ConcurrentMap,但是看過,Map的內部實現的同學應該可以發現,這個Map的實現和HashMap的實現簡直就是小巫見大巫,有沒有。它在構造函數中做了如下幾步:

  1. 初始化一個大小為16的Entry數組
  2. 通過上面說過的很迷的HashCode散列值與15取模得到將要存儲在數組中的索引值
  3. 構造Entry,然後保存進去
  4. 長度設置為1
  5. 設置要擴容的限制大小為16的2/3

我們看到這個不就是用數組實現的Map嘛,看過HashMap實現的我們,覺得洒洒水啦。
Map的set和get方法就不分析了。ThreadLocal的get方法我們還是要貼出來的,畢竟是我們主要分析的東西

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可見,是獲取到當前線程,用作key獲取到Map,然後用當前this獲取到Entry實體。最後當然獲取到了存儲的value。

我編碼,我快樂~

本文由博客一文多發平台 發布!

我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=19l4zaaz7g6ct

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

初識web API接口及Restful接口規範

一、web API接口

什麼是web API接口?:

明確了請求方式,提供對應後台所需參數,請求url鏈接可以得到後台的響應數據
url : 返回數據的url

請求方式:
get,post,put,patch….

請求參數:json或xml格式的key-value類型數據

  • ak:6E823f587c95f0148c19993539b99295
  • region:上海
  • query:肯德基
  • output:json

響應結果:
​ 返回json或xml格式的key-value類型數據

  • 上方請求參數的output參數值決定了響應數據的格式

  • {
        "status":0,
        "message":"ok",
        "results":[
            {
                "name":"肯德基(羅餐廳)",
                "location":{
                    "lat":31.415354,
                    "lng":121.357339
                },
                "address":"月羅路2380號",
                "province":"上海市",
                "city":"上海市",
                "area":"寶山區",
                "street_id":"339ed41ae1d6dc320a5cb37c",
                "telephone":"(021)56761006",
                "detail":1,
                "uid":"339ed41ae1d6dc320a5cb37c"
            }
            ...
            ]
    }

怎麼寫接口?:

​ 參照某種規則(規範)書寫url鏈接,同時根據規則制定請求方式,請求數據與響應結果

接口文檔:

​ 提供給前後台開發人員與測試人員查看

接口工具:

YAPI平台

​ 專門寫接口文檔的YAPI平台

​ YApi是去哪兒網的前端技術中心的一個開源可視化接口管理平台。

​

  • 創建接口項目
  • 創建接口
  • 編寫接口

接口測試工具:Postman

Postman是一款接口調試工具,是一款免費的可視化軟件,同時支持各種操作系統平台,是測試接口的首選工具。

Postman可以直接從下載獲得,然後進行傻瓜式安裝。

  • 工作面板
  • 簡易的get請求
  • 簡易的post請求
  • 案例:請求百度地圖接口

接口規範:

​ webapi接口規範:restful
​

RESTful API介紹

RESTful介紹

REST與技術無關,代表的是一種軟件架構風格,REST是Representational State Transfer的簡稱,中文翻譯為“表徵狀態轉移”或“表現層狀態轉化”。

推薦閱讀

RESTful API設計指南

推薦閱讀

API與用戶的通信協議

總是使用。

域名

用api關鍵字來標識接口url

注:看到api字眼,就代表該請求url鏈接是完成前後台數據交互的

版本

\1. 將版本信息放在URL中,如:

​

​

​ v1,v2代表不同數據版本的提現,前提是一種數據資源有多個版本

\2. 將版本信息放在請求頭中。

url路徑

視網絡上任何東西都是資源,均使用名詞表示(一般為複數形式)

在url鏈接中獎勵不要出現操作資源的動詞

​ 錯誤示範:

特殊的接口可以出現動詞,因為這些接口一般沒有一個明確的資源,或是動詞就是接口的核心含義

method請求方式

GET :從服務器取出資源(一項或多項)

POST :在服務器新建一個資源

PUT :在服務器更新資源(客戶端提供改變后的完整資源)

PATCH :在服務器更新資源(客戶端提供改變的屬性)

DELETE :從服務器刪除資源

過濾

通過在url上傳參的形式傳遞搜索條件

狀態碼

200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
202 Accepted - [*]:表示一個請求已經進入後台排隊(異步任務)
204 NO CONTENT - [DELETE]:用戶刪除數據成功。


301:永久重定向
302:暫時重定向


400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。
401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。
403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。


500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷髮出的請求是否成功。

錯誤處理

狀態碼是4xx時,應返回錯誤信息,error當做key。    

{
    error: "Invalid API key"
}

返回結果

針對不同操作,服務器向用戶返回的結果應該符合以下規範

GET     /collection:返回資源對象的列表(數組)
GET     /collection/resource:返回單個資源對象
POST    /collection:返回新生成的資源對象
PUT     /collection/resource:返回完整的資源對象
PATCH   /collection/resource:返回完整的資源對象
DELETE  /collection/resource:返回一個空文檔
{
    "status": 0,
    "msg": "ok",
    "results":[
        {
            "name":"肯德基(羅餐廳)",
            "location":{
                "lat":31.415354,
                "lng":121.357339
            },
            "address":"月羅路2380號",
            "province":"上海市",
            "city":"上海市",
            "area":"寶山區",
            "street_id":"339ed41ae1d6dc320a5cb37c",
            "telephone":"(021)56761006",
            "detail":1,
            "uid":"339ed41ae1d6dc320a5cb37c"
        }
        ...
        ]
}

Hypermedia API

RESTful API最好做到Hypermedia,即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什麼。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

本田計畫到2030年新能源汽車比例將達到三分之二

據《財富》網路版報導,本田週三表示,計畫把新能源汽車比例由當前的5%提高到2030年的三分之二。

本田在發佈的最新戰略中稱,在未來不到15年,油田混合動力、插電式混合動力、電池動力和燃料電池動力汽車產量將超過燃油動力汽車。

本田首席執行官八鄉隆弘(Takahiro Hachigo)說,插電式混合動力汽車將是公司“未來電氣化的核心”。

八鄉隆弘說,本田將於2018年在北美推出一款插電式混合動力汽車,然後會推出主要車型的插電式版本,並增加車型數量。
 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

第六屆中國國際新能源汽車論壇2016-頒獎典禮已開啟評獎模式

由希邁商務諮詢(上海)有限公司主辦的2016年第六屆中國國際新能源汽車論壇即將於4月20日-4月22日在上海隆重舉行。此次論壇獲得了亞太電動車學會、國家新能源機動車產品品質監督檢驗中心、上海交大密西根學院、賽迪顧問及中國綠色能源產業技術創新戰略聯盟的大力支持。截止2月19日,論壇已經確認39位演講嘉賓出席本次論壇並做高品質學術演講。演講嘉賓分別來自菲律賓電動車協會、國家新能源機動車產品品質監督檢驗中心、美國國家能源局、中國工程院等在內的政府單位與研究機構,以及包括寶馬、通用汽車、特斯拉、長安汽車、長城汽車、比亞迪戴姆勒、上汽、北汽、觀致等在內的多個知名整車商,將在論壇上共同研討新能源汽車行業政策趨勢、技術路線及難點、基礎設施建設、商業模式,延續以往的豐碩成果,繼續為新能源汽車行業作出貢獻。

此外,為了表彰各類汽車企業在行業內的傑出貢獻,第六屆中國國際新能源汽車論壇還將舉辦頒獎典禮,並設立了多個獎項,將由中國工程院院士陳清泉領銜的多個知名業內專家作為頒獎嘉賓為這些企業授予頒獎,已經有多家企業提交了獎項申請表及資料,參與反響積極。

本屆論壇相比歷屆舉辦規模最大的第六屆新能源汽車論壇,涉及主論壇及三個分論壇、考察活動、頒獎典禮和交流晚宴。屆時將誠邀全球範圍內的整車製造商、電網電力公司、電池廠商、零部件供應商、核心技術提供商和政府官員近500位行業人士一起,對新能源汽車產業面臨的挑戰,機遇與對策各方面進行為期三天更深層次並具有建設和戰略性的探討。

會議結構

會議亮點

豐富的內容:

9大板塊的深度解析

參會嘉賓:

500+高度滿意的企業決策者,120+業內知名企業,30+國家和地區

參會嘉賓分析:

17%+來自各國政府部門及權威機構,25%+來自知名整車商

演講嘉賓:

40+世界新能源汽車行業知名發言嘉賓

交流機會:

24+小時的交流機會:考察討論、VIP晚宴和開放式問答

會議形式:

1個主論壇,3個分論壇,1個晚宴,1個頒獎典禮以及1天考察活動

若您對峰會有更多要求,請撥打021-6045 1760與我們聯繫,謝謝理解和支持!
我們期待與貴單位一起出席於2016年4月20-22日在上海舉辦的第六屆中國國際新能源汽車論壇2016,以利決策!
想瞭解詳細內容,請登陸官方網站:  
連絡人:Hill ZENG(曾先生)
電話:0086 21-6045 1760
傳真:0086 21-6047 5887
郵箱:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※台灣海運大陸貨務運送流程

※兩岸物流進出口一站式服務

Tim Cook:汽車是蘋果給用戶準備的聖誕禮物

在 2016 年蘋果公司股東大會上,該公司 CEO Tim Cook 在回答股東關於蘋果汽車的詢問時,他表示用戶等待這一款新品就是等待聖誕禮物,現在距離耶誕節還有一段時間。蘋果公司已經從 Tesla、Ford、GM、Samsung 等公司招募了數百名工程師,組成了電動車研發團隊。

蘋果正在研發電動車已經不是什麼新聞了,但一直沒有官方消息證實,該公司 CEO Tim Cook 在股東大會上回應了股東有關蘋果汽車的問題,他表示現在就像兒童時代,等待平安夜的時間,期待將收到什麼禮物,未來一段時間,就像是等待禮物的平安夜。這意味蘋果公司正在研發電動汽車,但上市還需要等一段時間。   除了電動汽車外,此次股東大會還討論其他議題,蘋果在 2016 的研發支出為 85 億美元,並收購了 19 家公司,目前該公司的員工總數大約為 11.6 萬人。關於近日廣受關注的 FBI 要求解鎖 iPhone 的事件,Tim Cook 再次重申了蘋果公司立場,堅決擁護用戶的個人隱私和資訊安全。蘋果公司的新總部即將完工,大約在 2017 年 1 月部分員工就將搬入新的員工,這是 Steve Jobs 生前最關注的專案之一。

(首圖來源: CC BY 2.0)    (本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!