分類
發燒車訊

MachO文件詳解–逆向開發

今天是逆向開發的第5天內容–MachO文件(Mac 和 iOS 平台可執行的文件),在逆向開發中是比較重要的,下面我們着重講解一下MachO文件的基本內容和使用。

一、MachO概述

1. 概述

Mach-O是Mach Object文件格式的縮寫,iOS以及Mac上可執行的文件格式,類似Window的exe格式,Linux上的elf格式。Mach-O是一個可執行文件、動態庫以及目標代碼的文件格式,是a.out格式的替代,提供了更高更強的擴展性。

2.常見格式

Mach-O常見格式如下:

  • 目標文件 .o
  • 庫文件
  1. .a
  2. .dylib
  3. .framework
  • 可執行文件
  • dyld
  • .dsym

  通過file文件路徑查看文件類型

我們通過部分實例代碼來簡單研究一下。

2.1目標文件.o

通過test.c 文件,可以使用clang命令將其編譯成目標文件.o

我們再通過file命令(如下)查看文件類型

是個Mach-O文件。

2.2 dylib

通過cd /usr/lib命令查看dylib

通過file命令查看文件類型

 

 2.3 .dsym

下面是一個截圖來說明.dsym是也是Mach-O文件格式

 

以上只是Mach-O常見格式的某一種,大家可以通過命令來嘗試。

3. 通用二進制文件

希望大家在了解App二進制架構的時候,可以先讀一下本人的另一篇博客關於armv7,armv7s以及arm64等的介紹。

通用二進制文件是蘋果自身發明的,基本內容如下

下面通過指令查看Macho文件來看下通用二進制文件

 

然後通過file指令查看文件類型

 

上面該MachO文件包含了3個架構分別是arm v7,arm v7s 以及arm 64 。

針對該MachO文件我們做幾個操作,利用lipo命令拆分合併架構

3.1 利用lipo-info查看MachO文件架構

3.2 瘦身MachO文件,拆分

利用lipo-thin瘦身架構

 

 查看一下結果如下,多出來一個新建的MachO_armv7

 

3.3 增加架構,合併

利用lipo -create 合併多種架構

發現多出一種框架,合併成功多出Demo可執行文件。結果如下:

 

整理出lipo命令如下:

 

二、MachO文件

2.1 文件結構

下面是蘋果官方圖解釋MachO文件結構圖

MachO文件的組成結構如上,看包括了三個部分

  • Header包含了該二進制文件的一般信息,信息如下:
  1. 字節順序、加載指令的數量以及架構類型
  2. 快速的確定一些信息,比如當前文件是32位或者64位,對應的文件類型和處理器是什麼
  • Load commands 包含很多內容的表
  1. 包括區域的位置、動態符號表以及符號表等
  • Data一般是對象文件的最大部分
  1. 一般包含Segement具體數據

2.2 Header的數據結構

在項目代碼中,按下Command+ 空格,然後輸入loader.h  

然後查看loader.h文件,找到mach_header

上面是mach_header,對應結構體的意義如下:

通過MachOView查看Mach64 Header頭部信息

2.3 LoadCommands

LoadCommand包含了很多內容的表,通過MachOView查看LoadCommand的信息,圖如下:

 

但是大家看的可能並不了解內容,下面有圖進行註解,可以看下主要的意思

2.4 Data

Data包含Segement,存儲具體數據,通過MachOView查看,地址映射內容

 

三、DYLD

3.1 dyld概述

dyld(the dynamic link editor)是蘋果動態鏈接器,是蘋果系統一個重要的組成部分,系統內核做好準備工作之後,剩下的就會交給了dyld。

3.2 dyld加載過程

程序的入口一般都是在main函數中,但是比較少的人關心main()函數之前發生了什麼?這次我們先探索dyld的加載過程。(但是比在main函數之前,load方法就在main函數之前)

3.2.1 新建項目,在main函數下斷

 

main()之前有個libdyld.dylib start入口,但是不是我們想要的,根據dyld源碼找到__dyld_start函數

3.2.2 dyld main()函數

dyld main()函數是關鍵函數,下面是函數實現內容。(此時的main實現函數和程序App的main 函數是不一樣的,因為dyld也是一個可執行文件,也是具有main函數的

//
// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    // Grab the cdHash of the main executable from the environment
    // 第一步,設置運行環境
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
        // 獲取主程序的hash
        mainExecutableCDHash = mainExecutableCDHashBuffer;

    // Trace dyld's load
    notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
#if !TARGET_IPHONE_SIMULATOR
    // Trace the main executable's load
    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif

    uintptr_t result = 0;
    // 獲取主程序的macho_header結構
    sMainExecutableMachHeader = mainExecutableMH;
    // 獲取主程序的slide值
    sMainExecutableSlide = mainExecutableSlide;

    CRSetCrashLogMessage("dyld: launch started");
    // 設置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // Pickup the pointer to the exec path.
    // 獲取主程序路徑
    sExecPath = _simple_getenv(apple, "executable_path");

    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if (!sExecPath) sExecPath = apple[0];

    if ( sExecPath[0] != '/' ) {
        // have relative path, use cwd to make absolute
        char cwdbuff[MAXPATHLEN];
        if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
            // maybe use static buffer to avoid calling malloc so early...
            char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
            strcpy(s, cwdbuff);
            strcat(s, "/");
            strcat(s, sExecPath);
            sExecPath = s;
        }
    }

    // Remember short name of process for later logging
    // 獲取進程名稱
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;
    
    // 配置進程受限模式
    configureProcessRestrictions(mainExecutableMH);


    // 檢測環境變量
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    // 如果設置了DYLD_PRINT_OPTS則調用printOptions()打印參數
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    // 如果設置了DYLD_PRINT_ENV則調用printEnvironmentVariables()打印環境變量
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
    // 獲取當前程序架構
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    //-------------第一步結束-------------
    
    // load shared cache
    // 第二步,加載共享緩存
    // 檢查共享緩存是否開啟,iOS必須開啟
    checkSharedRegionDisable((mach_header*)mainExecutableMH);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        mapSharedCache();
    }
    ...

    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();

        // instantiate ImageLoader for main executable
        // 第三步 實例化主程序
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
    #endif


        // dyld_all_image_infos image list does not contain dyld
        // add it as dyldPath field in dyld_all_image_infos
        // for simulator, dyld_sim is in image list, need host dyld added
#if TARGET_IPHONE_SIMULATOR
        // get path of host dyld from table of syscall vectors in host dyld
        void* addressInDyld = gSyscallHelpers;
#else
        // get path of dyld itself
        void*  addressInDyld = (void*)&__dso_handle;
#endif
        char dyldPathBuffer[MAXPATHLEN+1];
        int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
        if ( len > 0 ) {
            dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
            if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
                gProcessInfo->dyldPath = strdup(dyldPathBuffer);
        }

        // load any inserted libraries
        // 第四步 加載插入的動態庫
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
                loadInsertedDylib(*lib);
        }
        // record count of inserted libraries so that a flat search will look at 
        // inserted libraries, then main, then others.
        // 記錄插入的動態庫數量
        sInsertedDylibCount = sAllImages.size()-1;

        // link main executable
        // 第五步 鏈接主程序
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // previous link() on main executable has already adjusted its internal pointers for ASLR
            // work around that by rebasing by inverse amount
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

        // link any inserted libraries
        // do this after linking main executable so that any dylibs pulled in by inserted 
        // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
        // 第六步 鏈接插入的動態庫
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
            // only INSERTED libraries can interpose
            // register interposing info after all inserted libraries are bound so chaining works
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->registerInterposing();
            }
        }

        // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing();
        }
        ...

        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        gLinkContext.linkingMainExecutable = false;
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        // 第七步 執行弱符號綁定
        sMainExecutable->weakBind(gLinkContext);

        // If cache has branch island dylibs, tell debugger about them
        if ( (sSharedCacheLoadInfo.loadAddress != NULL) && (sSharedCacheLoadInfo.loadAddress->header.mappingOffset >= 0x78) && (sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset != 0) ) {
            uint32_t count = sSharedCacheLoadInfo.loadAddress->header.branchPoolsCount;
            dyld_image_info info[count];
            const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCacheLoadInfo.loadAddress + sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset);
            // <rdar://problem/20799203> empty branch pools can be in development cache
            if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) {
                for (int poolIndex=0; poolIndex < count; ++poolIndex) {
                    uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheLoadInfo.slide;
                    info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr;
                    info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands";
                    info[poolIndex].imageFileModDate = 0;
                }
                // add to all_images list
                addImagesToAllImages(count, info);
                // tell gdb about new branch island images
                gProcessInfo->notification(dyld_image_adding, count, info);
            }
        }

        CRSetCrashLogMessage("dyld: launch, running initializers");
        ...
        // run all initializers
        // 第八步 執行初始化方法
        initializeMainExecutable(); 

        // notify any montoring proccesses that this process is about to enter main()
        dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN_DYLD2, 0, 0);
        notifyMonitoringDyldMain();

        // find entry point for main executable
        // 第九步 查找入口點並返回
        result = (uintptr_t)sMainExecutable->getThreadPC();
        if ( result != 0 ) {
            // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
            if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
            else
                halt("libdyld.dylib support not present for LC_MAIN");
        }
        else {
            // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
            result = (uintptr_t)sMainExecutable->getMain();
            *startGlue = 0;
        }
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }
    ...
    
    return result;
}

View Code

摺疊開dyld main函數,步驟總結如下

對待dyld的講述,是非常不易的,因為本身過程是比較複雜的,上面僅僅是自身的抽出來的。下面再畫一張流程圖,幫助大家理解。

 

四、總結

MachO文件對於逆向開發是非常重要的,通過本次講解,希望對大家理解逆向開發有所幫助,也希望大家真正可以提高技術,應對iOS市場的大環境,下一篇我們將講述Hook原理–逆向開發。謝謝!!!

 

 

 

 

 

 

 

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

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

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

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

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

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

分類
發燒車訊

Docker基礎與實戰,看這一篇就夠了

docker 基礎

什麼是Docker

Docker 使用 Google 公司推出的 Go 語言 進行開發實現,基於 Linux 內核的 cgroupnamespace,以及 AUFS 類的 Union FS 等技術,對進程進行封裝隔離,屬於 操作系統層面的虛擬化技術。由於隔離的進程獨立於宿主和其它的隔離的進程,因此也稱其為容器。

Docker 在容器的基礎上,進行了進一步的封裝,從文件系統、網絡互聯到進程隔離等等,極大的簡化了容器的創建和維護。使得 Docker 技術比虛擬機技術更為輕便、快捷。

記住最重要的一點,Dokcer實際是宿主機的一個普通的進程,這也是Dokcer與傳統虛擬化技術的最大不同。

為什麼要使用Docker

使用Docker最重要的一點就是Docker能保證運行環境的一致性,不會出現開發、測試、生產由於環境配置不一致導致的各種問題,一次配置多次運行。使用Docker,可更快地打包、測試以及部署應用程序,並可減少從編寫到部署運行代碼的周期。

docker 安裝

  • Docker 要求 CentOS 系統的內核版本高於 3.10 ,查看本頁面的前提條件來驗證你的CentOS 版本是否支持 Docker 。
    uname -r

  • 更新yum,升級到最新版本
    yum update

  • 卸載老版本的docker(若有)
    yum remove docker docker-common docker-selinux docker-engine
    執行該命令只會卸載Docker本身,而不會刪除Docker存儲的文件,例如鏡像、容器、卷以及網絡文件等。這些文件保存在/var/lib/docker 目錄中,需要手動刪除。

  • 查看yum倉庫,查看是否有docker
    ll /etc/yum.repos.d/

    如果用的廠商的服務器(阿里雲、騰訊雲)一般都會有docker倉庫,如果用的是虛擬機或者公司的服務器基本會沒有。

  • 安裝軟件包, yum-util 提供yum-config-manager功能,另外兩個是devicemapper驅動依賴的
    yum install -y yum-utils device-mapper-persistent-data lvm2

  • 安裝倉庫
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

  • 查看docker版本
    yum list docker-ce --showduplicates | sort -r

  • 安裝docker
    yum install docker-ce
    以上語句是是安裝最新版本的Docker,你也可以通過yum install docker-ce-<VERSION> 安裝指定版本

  • 啟動docker
    systemctl start docker

  • 驗證安裝是否正確
    dokcer run hello-world

docker 重要命令

鏡像相關

  • 搜索鏡像docker search
    docker search nginx Docker就會在Docker Hub中搜索含有“nginx”這個關鍵詞的鏡像倉庫

  • 下載鏡像docker pull
    docker pull nginx Docker就會在Docker Hub中下載含有“nginx”最新版本的鏡像
    當然也可以使用docker pull reg.jianzh5.com/nginx:1.7.9 下載指定倉庫地址標籤的nginx鏡像

  • 列出鏡像docker images

  • 刪除鏡像docker rmi
    docker rmi hello-world刪除我們剛剛下載的hello-world鏡像

  • 構建鏡像docker build
    通過Dockerfile構建鏡像,這個我們等下再拿出來詳細說明。

容器相關

  • 新建啟動鏡像docker run
    這個命令是我們最常用的命令,主要使用以下幾個選項
    ① -d選項:表示後台運行
    ② -P選項(大寫):隨機端口映射
    ③ -p選項(小寫):指定端口映射,前面是宿主機端口後面是容器端口,如docker run nginx -p 8080:80,將容器的80端口映射到宿主機的8080端口,然後使用localhost:8080就可以查看容器中nginx的歡迎頁了
    ④ -v選項:掛載宿主機目錄,前面是宿主機目錄,後面是容器目錄,如docker run -d -p 80:80 -v /dockerData/nginx/conf/nginx.conf:/etc/nginx/nginx.conf nginx 掛載宿主機的/dockerData/nginx/conf/nginx.conf的文件,這樣就可以在宿主機對nginx進行參數配置了,注意目錄需要用絕對路徑,不要使用相對路徑,如果宿主機目錄不存在則會自動創建。
    ⑤–rm : 停止容器後會直接刪除容器,這個參數在測試是很有用,如docker run -d -p 80:80 --rm nginx
    ⑥–name : 給容器起個名字,否則會出現一長串的自定義名稱如 docker run -name niginx -d -p 80:80 - nginx

  • 列出容器 docker ps
    這個命令可以列出當前運行的容器,使用-a參數后列出所有的容器(包括已停止的)

  • 停止容器docker stop
    docker stop 5d034c6ea010 後面跟的是容器ID,也可以使用容器名稱

  • 啟動停止的容器docker start
    docker run是新建容器並啟動,docker start 是啟動停止的容器,如docker start 5d034c6ea010

  • 重啟容器docker restart
    此命令執行的過程實際是先執行docker stop,然後再執行docker start,如docker restart 5d034c6ea010

  • 進入容器docker exec -it 容器id /bin/bash
    docker exec -it 5d034c6ea010 /bin/bash,就相當於進入了容器本身的操作系統

  • 刪除容器 docker rm
    docker rm 5d034c6ea010 後面跟的是容器ID,刪除容器之前需要先停止容器運行

  • 數據拷貝docker cp
    此命令用於容器與宿主機之間進行數據拷貝,如 docker cp 5d034c6ea010: /etc/nginx/nginx.conf /dockerData/nginx/conf/nginx.conf 將容器的目錄文件拷貝到宿主機指定位置,容器ID可以替換成容器名。

命令實戰

如果我們需要一個nginx容器,並且需要在宿主機上直接修改nginx的配置文件、默認主頁,在宿主機可以實時看到容器nginx的日誌。我們可以按照如下的方式一步一步完成。

  • 使用–rm參數啟動容器,方便刪除
    docker run -d -p 8081:80 --name nginx --rm nginx

  • 進入容器,查看容器中配置文件、項目文件、日誌文件的目錄地址
    docker exec -it 9123b67e428e /bin/bash

  • 導出容器的配置文件
    docker cp nginx:/etc/nginx/nginx.conf /dockerData/nginx/conf/nginx.conf導出配置文件 nginx.conf
    docker cp nginx:/etc/nginx/conf.d /dockerData/nginx/conf/conf.d導出配置目錄 conf.d

  • 停止容器docker stop 9123b67e428e,由於加了–rm參數,容器會自動刪除

  • 再以如下命令啟動容器,完成目錄掛載
    shell docker run -d -p 8081:80 --name nginx \ -v /dockerData/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \ -v /dockerData/nginx/conf/conf.d:/etc/nginx/conf.d \ -v /dockerData/nginx/www:/usr/share/nginx/html \ -v /dockerData/nginx/logs:/var/log/nginx nginx
  • 訪問服務器地址http://192.168.136.129:8081/

    訪問報錯,這時候就進入宿主機的日誌目錄/dockerData/nginx/logs查看日誌
    2019/11/23 10:08:11 [error] 6#6: *1 directory index of “/usr/share/nginx/html/” is forbidden, client: 192.168.136.1, server: localhost, request: “GET / HTTP/1.1”, host: “192.168.136.129:8081”
    因為/usr/share/nginx/html/被掛載到了服務器上面的/dockerData/nginx/www目錄下,原來的歡迎頁面在dockerData/nginx/www是沒有的,所有就報錯了,這裏我們隨便建一個。

  • 建立默認主頁
    shell #打開項目文件 cd /dockerData/nginx/www #使用vim 創建並編輯文件 vi index.html #此時我們會進入vim界面,按 i 插入,然後輸入 <h1 align="center">Hello,Welcome to Docker World</h1> #輸入完后,按 esc,然後輸入 :wq
  • 再次訪問瀏覽器地址

Dockerfile

我們可以使用Dockfile構建一個鏡像,然後直接在docker中運行。Dockerfile文件為一個文本文件,裡面包含構建鏡像所需的所有的命令,首先我們來認識一下Dockerfile文件中幾個重要的指令。

指令詳解

  • FROM
    選擇一個基礎鏡像,然後在基礎鏡像上進行修改,比如構建一個SpringBoot項目的鏡像,就需要選擇java這個基礎鏡像,FROM需要作為Dockerfile中的第一條指令
    如:FROM openjdk:8-jdk-alpine 基礎鏡像如果可以的話最好使用alpine版本的,採用alpline版本的基礎鏡像構建出來的鏡像會小很多。

  • RUN
    RUN指令用來執行命令行命令的。它有一下兩種格式:

    • shell 格式:RUN ,就像直接在命令行中輸入的命令一樣。 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
    • exec 格式:RUN [“可執行文件”, “參數1”, “參數2”],這更像是函數調用中的格式。
  • CMD
    此指令就是用於指定默認的容器主進程的啟動命令的。
    CMD指令格式和RUN相似,也是兩種格式
    • shell 格式:CMD
    • exec 格式:CMD [“可執行文件”, “參數1”, “參數2″…]
    • 參數列表格式:CMD [“參數1”, “參數2″…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具體的參數。
  • ENTRYPOINT
    ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。 ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程序及參數。ENTRYPOINT 在運行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數 --entrypoint 來指定。
    當指定了 ENTRYPOINT 后,CMD 的含義就發生了改變,不再是直接的運行其命令,而是將 CMD 的內容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變為:
    <ENTRYPOINT> "<CMD>"

  • COPY & ADD
    這2個指令都是複製文件,它將從構建上下文目錄中   的文件/目錄 複製到新的一層的鏡像內的   位置。比如: COPY demo-test.jar app.jarADD demo-test.jar app.jar
    ADD指令比 COPY高級點,可以指定一個URL地址,這樣Docker引擎會去下載這個URL的文件,如果 ADD後面是一個 tar文件的話,Dokcer引擎還會去解壓縮。
    我們在構建鏡像時盡可能使用 COPY,因為 COPY 的語義很明確,就是複製文件而已,而 ADD 則包含了更複雜的功能,其行為也不一定很清晰。

  • EXPOSE
    聲明容器運行時的端口,這隻是一個聲明,在運行時並不會因為這個聲明應用就會開啟這個端口的服務。在 Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另一個用處則是在運行時使用隨機端口映射時,也就是 docker run -P 時,會自動隨機映射 EXPOSE 的端口。
    要將 EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區分開來。-p,是映射宿主端口和容器端口,換句話說,就是將容器的對應端口服務公開給外界訪問,而 EXPOSE 僅僅是聲明容器打算使用什麼端口而已,並不會自動在宿主進行端口映射。

  • ENV
    這個指令很簡單,就是設置環境變量,無論是後面的其它指令,如 RUN,還是運行時的應用,都可以直接使用這裏定義的環境變量。它有如下兩種格式:
    • ENV <key> <value>
    • ENV <key1>=<value1> <key2>=<value2>...
  • VOLUME
    該指令使容器中的一個目錄具有持久化存儲的功能,該目錄可被容器本身使用,也可共享給其他容器。當容器中的應用有持久化數據的需求時可以在Dockerfile中使用該指令。如VOLUME /tmp
    這裏的 /tmp 目錄就會在運行時自動掛載為匿名卷,任何向 /tmp 中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。當然,運行時可以覆蓋這個掛載設置。比如:
    docker run -d -v mydata:/tmp xxxx

  • LABEL
    你可以為你的鏡像添加labels,用來組織鏡像,記錄版本描述,或者其他原因,對應每個label,增加以LABEL開頭的行,和一個或者多個鍵值對。如下所示:
    LABEL version="1.0" LABEL description="test"

Dockerfile實戰

我們以一個簡單的SpringBoot項目為例構建基於SpringBoot應用的鏡像。
功能很簡單,只是對外提供了一個say接口,在進入這個方法的時候打印出一行日誌,並將日誌寫入日誌文件。

@SpringBootApplication
@RestController
@Log4j2
public class DockerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DockerApplication.class, args);
    }

    @GetMapping("/say")
    public String say(){
        log.info("get say request...");
        return "Hello,Java日知錄";
    }
    
}

我們使用maven將其打包成jar文件,放入一個單獨的文件夾,然後按照下面步驟一步步構建鏡像並執行

  • 在當前文件夾建立Dockerfile文件,文件內容如下:
    properties FROM openjdk:8-jdk-alpine #將容器中的/tmp目錄作為持久化目錄 VOLUME /tmp #暴露端口 EXPOSE 8080 #複製文件 COPY docker-demo.jar app.jar #配置容器啟動后執行的命令 ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
  • 使用如下命令構建鏡像
    docker built -t springboot:v1.0 .

    -t 指定鏡像的名稱及版本號,注意後面需要以 . 結尾。

  • 查看鏡像文件

  • 運行構建的鏡像
    docker run -v /app/docker/logs:/logs -p 8080:8080 --rm --name springboot springboot:v1.0

  • 瀏覽器訪問http://192.168.136.129:8080/say

  • 在宿主機上實時查看日誌
    tail -100f /app/docker/logs/docker-demo-info.log

    請關注個人公眾號:JAVA日知錄

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

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

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

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

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

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

分類
發燒車訊

this綁定方式總結

最近在回顧js的一些基礎知識,把《你不知道的js》系列又看了一遍,this始終是重中之重,還是決定把this相關知識做一個系統的總結,也方便自己日後回顧。

this的四條綁定規則

1.默認綁定

這是最常用的函數調用類型:獨立函數調用(即函數是直接使用不帶任何修飾的函數引用進行調用的)。可以把這條規則看作是無法應用其他規則時的默認規則。
默認綁定的this在非嚴格模式下指向window,嚴格模式下指向undefined,比如下面的函數foo在非嚴格模式下:

var a = 2;
function foo(){
    var a = 3;
    console.log(this.a);
}
foo(); //2

這裏的foo()方法內的this指向了window,因此window.a = 2;

嚴格模式下,this.指向undefined,因此訪問this.a會報錯:

var a = 2;
function foo(){
    "use strict";
    var a = 3;
    console.log(this.a);
}
foo(); //Uncaught TypeError: Cannot read property 'a' of undefined

2.隱式綁定

如果調用位置上有上下文對象,或者說被某個對象“擁有”或者“包
含”,則使用隱式綁定。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

上例中的foo是通過obj.foo()的方式調用的,調用位置會使用obj上下文來引用函數,因此foo中的this指向了obj。
另外foo是當做引用被加入到obj中的,但是無論是直接在obj 中定義還是先定義再添加為引用屬性,foo嚴格上來說都不屬於obj,因此上述定義裏面的“擁有”與“包含”加上了引號,這樣說是為了方便理解。
常見的隱式調用場景:
obj.fn();
arguments[i]();//其實就是將點的調用方式變為了[]調用
el.onClick(function(){console.log(this);//this指向el})

隱式丟失

先來看一段代碼:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函數別名!
var a = "global"; // a 是全局對象的屬性
bar(); // "global"

上述代碼其實只用看調用的方式:bar(),這其實是一個不帶任何修飾的函數調用,因此應用了默認綁定。
還有一種參數傳遞的方式也會發生隱式丟失,原理其實跟上述例子一樣:

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    // fn 其實引用的是foo
    fn(); // <-- 調用位置!
}
var obj = {
    a: 2,
    foo: foo
};
var a = "global"; // a 是全局對象的屬性
doFoo( obj.foo ); // "global"

显示綁定

使用call,apply和bind方法可以指定綁定函數的this的值,這種綁定方法叫显示綁定。

function foo() {
    console.log( this.a );
}
var obj = {
    a:2
};
foo.call( obj ); // 2

通過foo.call(obj),我們可以在調用foo 時強制把它的this 綁定到obj 上

new綁定

new操作符可以基於一個“構造函數”新創建一個對象實例,new的實例化過程如下:

  1. 創建(或者說構造)一個全新的對象。
  2. 這個新對象會被執行[[ 原型]] 連接。
  3. 這個新對象會綁定到函數調用的this。
  4. 如果函數沒有返回其他對象,那麼new 表達式中的函數調用會自動返回這個新對象。
    明確了new的實例化過程后,思考如下代碼:
function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2

new foo(2)后新創建了個實例對象bar,然後把這個新對象bar綁定到了foo函數中的this,因此執行this.a = a后其實是把a賦給了bar.a

優先級

一般情況下this的綁定會根據上述四條綁定規則來,那麼他們同時出現時,該以怎樣的順序來判斷this的指向?下面是具體的規則:

  1. 函數是否在new 中調用(new 綁定)?如果是的話this 綁定的是新創建的對象( var bar = new foo() )。
  2. 函數是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this 綁定的是指定的對象( var bar = foo.call(obj2) )。
  3. 函數是否在某個上下文對象中調用(隱式綁定)?如果是的話,this 綁定的是那個上下文對象。( var bar = obj1.foo() )
  4. 如果都不是的話,使用默認綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到全局對象。( var bar = foo() )

綁定例外

1.使用call,appy,bind這種顯式綁定的方法,參數傳入null或者undefined作為上下文時,函數調用還是會使用默認綁定

function foo() {
    console.log( this.a );
}
var a = 2;
foo.call( null ); // 2

什麼情況下需要將上下文傳為null呢?
1.使用bind函數來實現柯里化

function foo(a,b) {
    console.log(a,b);
}
// 使用 bind(..) 進行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // 2,3

2.使用apply(..) 來展開一個數組,併當作參數傳入一個函數

function foo(a,b) {
    console.log(a,b);
}
// 把數組展開成參數
foo.apply( null, [2, 3] ); // 2,3

其實上面兩種使用場景其實都不關心call/app/bind第一個參數的值是什麼,只是想傳個佔位值而已。
但是總是傳入null可能會出現一些難以追蹤的bug,比如說當你在使用的第三方庫中的某個函數中有this時,this會被錯誤的綁定到全局對象上,造成一些難以預料的後果(修改全局變量)

var a = 1;//全局變量
const Utils = {
    a: 2,
    changeA: function(a){
        this.a = a;
    }
}
Utils.changeA(3);
Utils.a //3
a //1
Utils.changeA.call(null,4);
Utils.a //3
a //4,修改了全局變量a!

更安全的做法:

var o = Object.create(null);
Utils.changeA.call(o,6);
a //1, 全局變量沒有修改
o.a // 6 改的是變量o

2.間接引用

function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2

賦值表達式p.foo = o.foo 的返回值是目標函數的引用,因此調用位置是foo() 而不是p.foo() 或者o.foo()。根據我們之前說過的,這裡會應用默認綁定。

this詞法(箭頭函數)

上述的幾種規則適用於所有的正常函數,但不包括ES6的箭頭函數。箭頭函數不使用this的四種標準規則,而是根據外層(函數或者全局)作用域(詞法作用域)來決定this

function foo() {
// 返回一個箭頭函數
    return (a) => {
        //this 繼承自foo()
        console.log( this.a );
    };
}
var obj1 = {
    a:2
};
var obj2 = {
    a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是3 !

foo() 內部創建的箭頭函數會捕獲調用時foo() 的this。由於foo() 的this 綁定到obj1,bar(引用箭頭函數)的this 也會綁定到obj1,箭頭函數的綁定無法被修改。(new 也不行!)

幾個例子加深理解

this的理論知識講解得差不多了,來幾個例子看看自己有沒有理解全面:
1.經典面試題:以下輸出結果是什麼

var length = 10;
function fn() {
    console.log(this.length);
}
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
obj.method(fn, 1);

obj中method方法裏面調用了兩次fn。第一次是直接調用的“裸露”的fn,因此fn()中this使用默認綁定,this.length為10.第二次調用時通過arguments的方式調用的,arguments[0]其實指向的就是fn,但是是通過obj[fn]這種對象上下文的隱式綁定的,因此this指向arguments,而arguments只有一個一項(method中只有fn一個參數),因此arguments.length為1。因此打印的結果為:

10
1

2.以下輸出什麼

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
        return fn();
    }
};
obj.getAge();

答案是嚴格模式下會報錯,非嚴格模式下輸出NaN
原因也是因為在調用obj.getAge()后,getAge方法內的this使用隱式綁定。但是return fn()的時候用的是“裸露的fn”使用默認綁定,fn裏面的this指向window或者undefined。
使用箭頭函數來修正this的指向:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj對象
        return fn();
    }
};
obj.getAge(); // 25

使用箭頭函數后,fn中的this在他的詞法分析階段就已經確定好了(即fn定義的時候),跟調用位置無關。fn的this指向外層的作用域(即getAge中的this)
3.以下輸出為什麼是’luo’

var A = function( name ){ 
    this.name = name;
};
var B = function(){ 
    A.apply(this,arguments);
};
B.prototype.getName = function(){ 
    return this.name;
};
var b=new B('sven');  // B {name: "luo"}
console.log( b.getName() ); // 輸出:  'luo'

執行new B(‘seven’)後會返回一個新對象b,並且B函數中的this會綁定到新對象b上,B的函數體內執行A.apply(this.arguments)也就是執行b.name = name;這個時候b的值就是{name:’luo’},所以b.getName()就能輸出’luo’啦~

實際在業務使用中,邏輯會更複雜一些,但是萬變不離其宗,都按照上面寫的規則來代入就好了

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

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

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

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

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

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

分類
發燒車訊

從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之十二Swagger(參數)使用二

  引言

  在 中提到了 Swagger 的基本使用,僅限於沒有參數,沒有驗證的那種api文檔生成,那麼這篇就連接上篇繼續,在一般具有安全性、權限等驗證的接口上,

  都會在header/url中加上請求者的秘鑰、簽名等,當然也有可能添加到body等其它地方, Swashbuckle.AspNetCore 都支持這些寫法。

  如何使用 — 下面將介紹兩種使用方式

兩種方式參數設置到何處都是在  In屬性上,屬性對於值如下:    參考

  • query: 參数字段值對應放在url中
  • header: 參數值對應放在header param中
  • body: 參數對應放到請求體中
  • path: 參數應該對應放到請求路徑  // 具體貌似沒用
  • formData: 參數對應放到請求表單中

  第一種:將一個或多個參數保護API的“securityDefinitions”添加到生成的Swagger中。

這種是直接在文檔的右上方添加一個 Authorize 按鈕,設置了值后,每一個請求都會在設置的位置上加上相應的值,在 上一篇隨筆中的 ConfigureServices 方法中,

對應位置 services.AddSwaggerGen(options =>{}) 中的  XmlComments  下 添加代碼如下:

                options.AddSecurityDefinition("token", new ApiKeyScheme
                {
                    Description = "token format : {token}",//參數描述
                    Name = "token",//名字
                    In = "header",//對應位置
                    Type = "apiKey"//類型描述
                });
                options.AddSecurityDefinition("sid", new ApiKeyScheme
                {
                    Description = "sid format : {sid}",//參數描述
                    Name = "sid",//名字
                    In = "header",//對應位置
                    Type = "apiKey"//類型描述
                });
                //添加Jwt驗證設置 設置為全局的,不然在代碼中取不到
                options.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> {
                    { "token", Enumerable.Empty<string>() },
                    { "sid", Enumerable.Empty<string>() },
                });

  添加完成后,運行起來看下效果,效果圖: 

 設置上對應值,調用測試方法,可以在header中取到剛設置到的值,

 這裡能看到,可以取到設置的參數了。這樣一來,在需要驗證的接口上,我們就可以通過接口文檔來測試了。基本不用再藉助postman等接口測試工具了。

但是,但是,這裡有一個問題,就是只要設置了參數值,每一次訪問都會在請求中帶上參數。

下面將介紹第二種方式,只給需要驗證用戶的接口上添加驗證參數。

  第二種:使用“filters”擴展Swagger生成器,來實現只在需要添加參數的方法上添加參數。複雜的可以根據自己的需求來添加對應參數

實現方式就是先新建一個類,名: SwaggerParameter ,實現 IOperationFilter 接口。SwaggerParameter 類代碼如下: 

    /// <summary>
    /// 自定義添加參數
    /// </summary>
    public class SwaggerParameter : IOperationFilter
    {
        /// <summary>
        /// 實現 Apply 方法
        /// </summary>
        /// <param name="operation"></param>
        /// <param name="context"></param>
        public void Apply(Operation operation, OperationFilterContext context)
        {
            if (operation.Parameters == null) operation.Parameters = new List<IParameter>();
            var attrs = context.ApiDescription.ActionDescriptor.AttributeRouteInfo;
            var t = typeof(BaseUserController);
            //先判斷是否是繼承用戶驗證類
            if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor && context.MethodInfo.DeclaringType?.IsSubclassOf(t) == true)
            {
                //再驗證是否允許匿名訪問
                var actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
                bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
                // 需要驗證的方法添加
                if (!isAnonymous)
                {
                    operation.Parameters.Add(new NonBodyParameter()
                    {
                        Name = "sid",
                        In = "header", //query header body path formData
                        Type = "string",
                        Required = true,//是否必選
                        Description = "登錄返回的sid"
                    });
                    operation.Parameters.Add(new NonBodyParameter()
                    {
                        Name = "token",
                        In = "header", //query header body path formData
                        Type = "string",
                        Required = true,//是否必選
                        Description = "登錄返回的token"
                    });
                }
            }
        }
    }

 運行起來后,進入到  文檔頁面,可以看到右上角的 Authorize 按鈕已經不見了,在不需要驗證的方法上,也找不到相應需要設置參數的輸入框。就只有在需要驗證的接口上才有。

參考Swagger文檔圖如下: 

參考代碼圖如下:

 

效果圖: 

  這樣一來設置也就完成了。從上面就能看出,就只有需要用戶驗證的接口才會有相應參數。 

 

我的設置方式是先定義了用戶驗證控制器類,讓需要用戶驗證的控制器繼承該控制器,然後在該控制器中不需要用戶驗證的接口上加上 AllowAnonymous 屬性 

設置fitter時就可以根據上面提到的兩個點來進行判斷是否需要加上參數,如果不是這樣實現的,可以根據自己的需求變更fitter類,來控制文檔的生成。 

 

以上若有什麼不對或可以改進的地方,望各位指出或提出意見,一起探討學習~ 

有需要源碼的可通過此 鏈接拉取 覺得還可以的給個 start 和點個 下方的推薦哦~~謝謝!

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

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

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

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

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

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

分類
發燒車訊

pod刪除主要流程源碼解析

本文以v1.12版本進行分析

當一個pod刪除時,client端向apiserver發送請求,apiserver將pod的deletionTimestamp打上時間。kubelet watch到該事件,開始處理。

syncLoop

kubelet對pod的處理主要都是在syncLoop中處理的。

func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
for {
...
        if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
            break
        }
...

與pod刪除主要在syncLoopIteration中需要關注的是以下這兩個。

func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    select {
    case u, open := <-configCh:
...
        switch u.Op {
...
        case kubetypes.UPDATE:
            handler.HandlePodUpdates(u.Pods)
...
    case <-housekeepingCh:
        if !kl.sourcesReady.AllReady() {
        } else {
            if err := handler.HandlePodCleanups(); err != nil {
                glog.Errorf("Failed cleaning pods: %v", err)
            }
        }
    }

第一個是由於發送給apiserver的DELETE請求觸發的,增加了deletionTimestamp的事件。這裏對應於kubetypes.UPDATE。也就是會走到HandlePodUpdates函數。

另外一個與delete相關的是每2s執行一次的來自於housekeepingCh的定時事件,用於清理pod,執行的是handler.HandlePodCleanups函數。這兩個作用不同,下面分別進行介紹。

HandlePodUpdates

先看HandlePodUpdates這個流程。只要打上了deletionTimestamp,就必然走到這個流程里去。

func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) {
    for _, pod := range pods {
...
        kl.dispatchWork(pod, kubetypes.SyncPodUpdate, mirrorPod, start)
    }
}

在HandlePodUpdates中,進而將pod的信息傳遞到dispatchWork中處理。

func (kl *Kubelet) dispatchWork(pod *v1.Pod, syncType kubetypes.SyncPodType, mirrorPod *v1.Pod, start time.Time) {
    if kl.podIsTerminated(pod) {
        if pod.DeletionTimestamp != nil {
            kl.statusManager.TerminatePod(pod)
        }
        return
    }
    // Run the sync in an async worker.
    kl.podWorkers.UpdatePod(&UpdatePodOptions{
        Pod:        pod,
        MirrorPod:  mirrorPod,
        UpdateType: syncType,
        OnCompleteFunc: func(err error) {
...

這裏首先通過判斷了kl.podIsTerminated(pod)判斷pod是不是已經處於了Terminated狀態。如果是的話,則不進行下面的kl.podWorkers.UpdatePod。

func (kl *Kubelet) podIsTerminated(pod *v1.Pod) bool {
    status, ok := kl.statusManager.GetPodStatus(pod.UID)
    if !ok {
        status = pod.Status
    }
    return status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(status.ContainerStatuses))
}

這個地方特別值得注意的是,並不是由了DeletionTimestamp就會認為是Terminated狀態,而是有DeletionTimestamp且所有的容器不在運行了。也就是說如果是一個正在正常運行的pod,是也會走到kl.podWorkers.UpdatePod中的。UpdatePod通過一系列函數調用,最終會通過異步的方式執行syncPod函數中進入到syncPod函數中。

func (kl *Kubelet) syncPod(o syncPodOptions) error {
...
    if !runnable.Admit || pod.DeletionTimestamp != nil || apiPodStatus.Phase == v1.PodFailed {
        var syncErr error
        if err := kl.killPod(pod, nil, podStatus, nil); err != nil {
...

在syncPod中,調用killPod從而對pod進行停止操作。

killPod

killPod是停止pod的主體。在很多地方都會使用。這裏主要介紹下起主要的工作流程。停止pod的過程主要發生在killPodWithSyncResult函數中。

func (m *kubeGenericRuntimeManager) killPodWithSyncResult(pod *v1.Pod, runningPod kubecontainer.Pod, gracePeriodOverride *int64) (result kubecontainer.PodSyncResult) {
    killContainerResults := m.killContainersWithSyncResult(pod, runningPod, gracePeriodOverride)
...
    for _, podSandbox := range runningPod.Sandboxes {
            if err := m.runtimeService.StopPodSandbox(podSandbox.ID.ID); err != nil {
...

killPodWithSyncResult的主要工作分為兩個部分。killContainersWithSyncResult負責將pod中的container停止掉,在停止后再執行StopPodSandbox。

func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, reason string, gracePeriodOverride *int64) error {
    if err := m.internalLifecycle.PreStopContainer(containerID.ID); err != nil {
        return err
    }
...
    err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)

killContainersWithSyncResult的主要工作是在killContainer中完成的,這裏可以看到,其中的主要兩個步驟是在容器中進行prestop的操作。待其成功后,進行container的stop工作。至此所有的應用容器都已經停止了。下一步是停止pause容器。而StopPodSandbox就是執行這一過程的。將sandbox,也就是pause容器停止掉。StopPodSandbox是在dockershim中執行的。

func (ds *dockerService) StopPodSandbox(ctx context.Context, r *runtimeapi.StopPodSandboxRequest) (*runtimeapi.StopPodSandboxResponse, error) {
...
if !hostNetwork && (ready || !ok) {
...
        err := ds.network.TearDownPod(namespace, name, cID, annotations)
...
    }
    if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil {

StopPodSandbox中主要的部分是先進行網絡卸載,再停止相應的容器。在完成StopPodSandbox后,至此pod的所有容器都已經停止完成。至於volume的卸載,是在volumeManager中進行的。本文不做單獨介紹了。停止后的容器在pod徹底清理后,會被gc回收。這裏也不展開講了。

HandlePodCleanups

上面這個流程並不是刪除流程的全部。一個典型的情況就是,如果container都不是running,那麼在UpdatePod的時候都return了,那麼又由誰來處理呢?這裏我們回到最開始,就是那個每2s執行一次的HandlePodCleanups的流程。也就是說比如container處於crash,container正好不是running等情況,其實是在這個流程里進行處理的。當然HandlePodCleanups的作用不僅僅是清理not running的pod,再比如數據已經在apiserver中強制清理掉了,或者由於其他原因這個節點上還有一些沒有完成清理的pod,都是在這個流程中進行處理。

func (kl *Kubelet) HandlePodCleanups() error {
... 
    for _, pod := range runningPods {
        if _, found := desiredPods[pod.ID]; !found {
            kl.podKillingCh <- &kubecontainer.PodPair{APIPod: nil, RunningPod: pod}
        }
    }

runningPods是從cache中獲取節點現有的pod,而desiredPods則是節點上應該存在未被停止的pod。如果存在runningPods中有而desiredPods中沒有的pod,那麼它應該被停止,所以發送到podKillingCh中。

func (kl *Kubelet) podKiller() {
...
    for podPair := range kl.podKillingCh {
...

        if !exists {
            go func(apiPod *v1.Pod, runningPod *kubecontainer.Pod) {
                glog.V(2).Infof("Killing unwanted pod %q", runningPod.Name)
                err := kl.killPod(apiPod, runningPod, nil, nil)
...
            }(apiPod, runningPod)
        }
    }
}

在podKiller流程中,會去接收來自podKillingCh的消息,從而執行killPod,上文已經做了該函數的介紹了。

statusManager

在最後,statusManager中的syncPod流程,將會進行檢測,通過canBeDeleted確認是否所有的容器關閉了,volume卸載了,cgroup清理了等等。如果這些全部完成了,則從apiserver中將pod信息徹底刪除。

func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
...
    if m.canBeDeleted(pod, status.status) {
        deleteOptions := metav1.NewDeleteOptions(0)
        deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID))
        err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions)
...

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

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

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

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

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

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

分類
發燒車訊

氫燃料沒未來?Honda改拚電動車,明年搶攻歐陸

本田汽車(Honda)計畫於2018年一次推出兩款全電動車,且宣告未來將以電動車作為發展主軸。

根據《BusinessInsider》報導表示,本田六月就已表示將打造中國專屬電動車,8月29日發佈的聲明稿則指明另一款電動車是為歐洲客戶所設計。

本田說,新城市電動概念車(Urban EV Concept)將在九月法蘭克福車展上亮相,這也是本田在歐洲第一款電動車,肩負打開歐洲電動車市場的任務。

目前本田僅有一款純電動車在美國上市,不過本田表示,2030年旗下三分之二的車型都要電動化,達成目標的方法將在法蘭克福車展上對外說明。

本田與日本同業豐田(Toyota)此前主要開發氫燃料車與混和動力車,但由上述可知本田策略已經轉向發展電動車,可能引領其它日本車廠跟進。

(本文內容由授權使用。圖片出處:)

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

DuckDuckGo切換到蘋果地圖

  匿名搜索引擎 DuckDuckGo 宣布地圖和地址相關的搜索將使用蘋果地圖。Google 被認為是一大隱私威脅,顯然以隱私作為主要賣點的 DuckDuckGo 不能也應該使用 Google 地圖。它可以選擇的全球地圖只剩下蘋果地圖、OpenStreetMap 和必應地圖等不多的幾個選擇。Google 地圖在功能和細節上仍然是最出色的,蘋果地圖的美國地圖足夠精細,但在其它國家它的地圖準確性就並不那麼令人滿意了。

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

Yelp的“嚇人”更新說明:訓練神經網絡刪除全部數據

  騰訊《一線》 紀振宇 1 月 16 日發自硅谷

  本地服務信息點評網站 Yelp 最近發生了一次可能釀成嚴重後果的運營事故:全部數據遭到刪除,幸而最終得以恢復。

  15 日,Yelp 在其移動應用的最新更新說明中稱:“我們向所有本周使用 app 過程中出現問題的用戶致歉。我們訓練了一個神經網絡用於去除應用中的所有程序缺陷,但它把所有數據都刪除了,我們只好恢復了所有數據。“

  在更新說明中公布這一事件的同時,Yelp 還不忘幽默一把,稱,“公平地說,至少短暫時間內(指所有數據被刪除后和數據恢復之前),我們是 100% 沒有 Bug 的。“

  根據 Yelp 近期應用更新說明显示, Yelp 應用最近的更新重點為去除各種漏洞、程序缺陷和改善應用功能,或許是由於程序故障排查已經成為一個常規工作,因而 Yelp 計劃訓練一套神經網絡來進行程序故障的自動排查,但卻意外發生了刪除所有數據的事故。

  Yelp 成立於 2004 年,由兩名原 Paypal 的員工創辦,被認為是本地信息服務點評的鼻祖,於 2012 年上市,目前市值約為 30 億美元。

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

雷諾日產2022目標:推零排放電動車、降低車用鋰電池成本30%

特斯拉(Tesla Inc.)讓電動車產業起了大震盪,發展愈來愈快,雷諾日產聯盟(The Renault-Nissan Alliance)也不落人後,聲稱2020年將發表12款零排放電動車,同時還要推出機器人計程車(robo-taxi),提供無人駕駛的叫車服務。

CNBC報導,雷諾日產聯盟董事長兼執行長、三菱汽車董事長戈恩(Carlos Ghosn)15日在接受專訪時表示,集團擬定的六年新計畫「Alliance 2022」,除了要發表零排放電動車、機器人計程車之外,也會推出40款自駕程度不等的汽車款式。

戈恩說,機器人計程車主要是商業用途,許多像是Uber等業者,對此都有極高需求。另外,戈恩也打算將雷諾、日產與三菱汽車的合作綜效倍增,預估2022年聯盟的年營收,將從原本的50億歐元拉高至100億歐元;到了2022年,三大車廠的合併汽車銷售量,有望從2016年的1,000萬台、1,050萬台進一步增加至1,400萬台。
目前聯盟的主要目標,是降低車用電池的成本,希望能在2022年底前,將成本降低30%,單次充電時間壓縮到15分鐘、每次充電可行駛的里程數增加至230公里。戈恩說,集團已經評估過鋰的產能,估計未來5-6年內,應該都不會有瓶頸出現。

先前已有消息指稱,中國打算禁用、禁產傳統汽油車。新華社9月9日報導,中國工信部副部長辛國斌表示,一些國家已經制定了停止生產銷售傳統能源汽車的時間表。他說,目前工信部也啟動了相關研究、將會同相關部門制定中國的時間表。

Fortune 11日報導,目前北京當局並未對何時要進行轉換設定時間表。其他國家則打算在2030年左右逐步將汽油車淘汰:印度的目標設定在2030年、但並不是嚴格執行的時間點,至於英國、法國與挪威的預定目標則分別是2040年、2040年與2025年。

澳洲礦商Orocobre Limited最近才聲稱鋰礦需求強、報價攀高,將擴產碳酸鋰。barron`s.com、The Australian報導,Orocobre執行長Richard Seville 8月底說,全球鋰礦市場的基本面依舊穩健,不但需求強勁、供給吃緊,報價也相當具有吸引力。為了滿足特定電池夥伴的需求,Orocobre會分階段擴充Olaroz鋰礦廠的產能。

依據Orocobre計畫,位於阿根廷的Olaroz鋰礦廠,產能料將倍增。另外,該公司還會跟豐田通商(Toyota Tsusho Corporation)一同打造一座廠房,預計將年產10,000公噸的氫氧化鋰(lithium hydroxide)。

(本文內容由授權使用)

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

戴森宣示砸20億英鎊開發電動車、拚2020年公開亮相

戴森公司(Dyson)創辦人James Dyson 26日宣布,戴森開發中的電動車預計在2020年推出。Dyson預計投入20億英鎊開發電池驅動車、目前已打造出一支超過400人的研發團隊。他表示,汽車業競爭相當激烈、因此現階段無法對外公布更多細節。

倫敦國王學院研究人員發現,倫敦每年因長期遭受空氣污染而早死的人數逼近9,500人。世界衛生組織(WHO)報告顯示,2012年約有700萬人因暴露於空氣污染環境而死亡。

Thomson Reuters報導,Dyson 26日透露,研發團隊已經花了2.5年的時間在英國馬姆斯伯里(Malmesbury)開發固態電池技術。他還沒決定要在何處生產戴森電動車,但可以確定的是不會跟任何汽車製造商合作。

《日經亞洲評論》於2016年3月報導,戴森於2015年收購自密西根大學分割出來的固態電池技術開發公司Sakti3。戴森執行長Max Conze當時表示、希望3-5年內在能源儲存密度領域能夠有突破性的進展。

forbes.com 9月26日報導,與特斯拉(Tesla)執行長馬斯克(Elon Musk)不同的是,James Dyson已經擁有開發成功、可獲利消費性商品的紀錄。戴森產品包括吸塵器、吹風機、空氣清淨器、節能照明設備等等。

戴姆勒(Daimler AG)卡車部門9月14日宣布投資以色列奈米技術材料新創公司StoreDot Ltd.。cityam.com 5月報導,StoreDot名列百大最具破壞性新創企業第一名。

StoreDot官網顯示,採用FlashBattery技術的新世代電池僅需5分鐘就能完成充電工作、提供電動車多達300英里(480公里)的續航力,相當於充電1分鐘就能讓電動車跑60英里。

CNBC 9月5日報導,豐田汽車公司會長內山田竹志(Takeshi Uchiyamada)在接受專訪時表示,基於當前電池技術的侷限、他懷疑消費者會立即投向電動車的懷抱。內山田表示,豐田不是排斥電動車,但為了要提供足夠的續航力、電動車目前需要安裝許多電池並得花相當長的時間去充電,而且電池壽命也是一大問題。內山田認為車輛全面由電池驅動之前、還須經歷2-3次的技術性突破才行。

(本文內容由授權使用)  

 

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

【其他文章推薦】

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

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

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

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

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