天天看點

mapred linuxtaskcontroller目錄權限問題探究

 今天發現測試環境的kerberos hadoop的hive不能跑了,具體表現是select * limit這種不走mapred的job是ok的,走mapred的job就會報錯,報的錯比較奇怪(Unable to retrieve URL for Hadoop Task logs. Unable to find job tracker info port.)但是确認jobtracker是ok的,配置檔案也是正常的,看來和jobtracker沒有關系,進一步分析tasktracker的日志,發現如下錯誤。。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>2014-03-26 17:28:02,048 WARN org.apache.hadoop.mapred.TaskTracker: Exception </code><code>while</code> <code>localization java.io.IOException: Job initialization failed (24) with output: File </code><code>/home/test/platform</code> <code>must be owned by root, but is owned by 501</code>

<code>        </code><code>at org.apache.hadoop.mapred.LinuxTaskController.initializeJob(LinuxTaskController.java:194)</code>

<code>        </code><code>at org.apache.hadoop.mapred.TaskTracker$4.run(TaskTracker.java:1420)</code>

<code>        </code><code>at java.security.AccessController.doPrivileged(Native Method)</code>

<code>        </code><code>at javax.security.auth.Subject.doAs(Subject.java:396)</code>

<code>        </code><code>at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1407)</code>

<code>        </code><code>at org.apache.hadoop.mapred.TaskTracker.initializeJob(TaskTracker.java:1395)</code>

<code>        </code><code>at org.apache.hadoop.mapred.TaskTracker.localizeJob(TaskTracker.java:1310)</code>

<code>        </code><code>at org.apache.hadoop.mapred.TaskTracker.startNewTask(TaskTracker.java:2727)</code>

<code>        </code><code>at org.apache.hadoop.mapred.TaskTracker$TaskLauncher.run(TaskTracker.java:2691)</code>

<code>Caused by: org.apache.hadoop.util.Shell$ExitCodeException:</code>

<code>        </code><code>at org.apache.hadoop.util.Shell.runCommand(Shell.java:261)</code>

<code>        </code><code>at org.apache.hadoop.util.Shell.run(Shell.java:188)</code>

<code>        </code><code>at org.apache.hadoop.util.Shell$ShellCommandExecutor.execute(Shell.java:381)</code>

<code>        </code><code>at org.apache.hadoop.mapred.LinuxTaskController.initializeJob(LinuxTaskController.java:187)</code>

<code>        </code><code>... 8 </code><code>more</code>

其中/home/test/platform是mapred程式所在目錄,通過更改/home/test/platform的屬主為root解決,不過這個為什麼需要是root使用者呢

從調用棧資訊看到,是在調用LinuxTaskController類(因為用到了kerberos,taskcontroller需要選擇這個類)的initializeJob出錯了。initializeJob方法是對job做初始操作,傳入user,jobid,token,mapred的local dir等參數,生成一個數組,并調用ShellCommandExecutor的構造方法進行執行個體化,最終調用ShellCommandExecutor類的execute方法。

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

<code>public</code> <code>void</code> <code>initializeJob(String user, String jobid, Path credentials,</code>

<code>                          </code><code>Path jobConf, TaskUmbilicalProtocol taskTracker,</code>

<code>                          </code><code>InetSocketAddress ttAddr</code>

<code>                          </code><code>) </code><code>throws</code> <code>IOException {</code>

<code>  </code><code>List&lt;String&gt; command = </code><code>new</code> <code>ArrayList&lt;String&gt;(</code>

<code>    </code><code>Arrays.asList(taskControllerExe ,  </code><code>//task-controller</code>

<code>                  </code><code>user,</code>

<code>                  </code><code>localStorage.getDirsString(),         </code><code>//mapred.local.dir </code>

<code>                  </code><code>Integer. toString(Commands.INITIALIZE_JOB.getValue()),</code>

<code>                  </code><code>jobid,</code>

<code>                  </code><code>credentials.toUri().getPath().toString(),    </code><code>//jobToken</code>

<code>                  </code><code>jobConf.toUri().getPath().toString())); </code><code>//job.xml</code>

<code>  </code><code>File jvm =                                  </code><code>// use same jvm as parent</code>

<code>    </code><code>new</code> <code>File( </code><code>new</code> <code>File(System.getProperty( </code><code>"java.home"</code><code>), </code><code>"bin"</code> <code>), </code><code>"java"</code> <code>);</code>

<code>  </code><code>command.add(jvm.toString());</code>

<code>  </code><code>command.add(</code><code>"-classpath"</code><code>);</code>

<code>  </code><code>command.add(System.getProperty(</code><code>"java.class.path"</code> <code>));</code>

<code>  </code><code>command.add(</code><code>"-Dhadoop.log.dir="</code> <code>+ TaskLog.getBaseLogDir());</code>

<code>  </code><code>command.add(</code><code>"-Dhadoop.root.logger=INFO,console"</code><code>);</code>

<code>  </code><code>command.add(JobLocalizer.</code><code>class</code><code>.getName());  </code><code>// main of JobLocalizer</code>

<code>  </code><code>command.add(user);</code>

<code>  </code><code>command.add(jobid);</code>

<code>  </code><code>// add the task tracker's reporting address</code>

<code>  </code><code>command.add(ttAddr.getHostName());</code>

<code>  </code><code>command.add(Integer.toString(ttAddr.getPort()));</code>

<code>  </code><code>String[] commandArray = command.toArray( </code><code>new</code> <code>String[</code><code>0</code><code>]);</code>

<code>  </code><code>ShellCommandExecutor shExec = </code><code>new</code> <code>ShellCommandExecutor(commandArray);</code>

<code>  </code><code>if</code> <code>(LOG.isDebugEnabled()) {</code>

<code>    </code><code>LOG.debug( </code><code>"initializeJob: "</code> <code>+ Arrays.toString(commandArray));   </code><code>//commandArray</code>

<code>  </code><code>}</code>

<code>  </code><code>try</code> <code>{</code>

<code>    </code><code>shExec.execute();</code>

<code>    </code><code>if</code> <code>(LOG.isDebugEnabled()) {</code>

<code>      </code><code>logOutput(shExec.getOutput());</code>

<code>    </code><code>}</code>

<code>  </code><code>} </code><code>catch</code> <code>(ExitCodeException e) {</code>

<code>    </code><code>int</code> <code>exitCode = shExec.getExitCode();</code>

<code>    </code><code>logOutput(shExec.getOutput());</code>

<code>    </code><code>throw</code> <code>new</code> <code>IOException(</code><code>"Job initialization failed ("</code> <code>+ exitCode +</code>

<code>        </code><code>") with output: "</code> <code>+ shExec.getOutput(), e);</code>

<code>}</code>

通過打開tasktracker的debug日志,可以擷取commandArray的具體資訊:

<code>2014</code><code>-</code><code>03</code><code>-</code><code>26</code> <code>19</code><code>:</code><code>49</code><code>:</code><code>02</code><code>,</code><code>489</code> <code>DEBUG org.apache.hadoop.mapred.LinuxTaskController: initializeJob:</code>

<code>[/home/test/platform/hadoop-</code><code>2.0</code><code>.</code><code>0</code><code>-mr1-cdh4.</code><code>2.0</code><code>/bin/../sbin/Linux-amd64-</code><code>64</code><code>/task-controller,</code>

<code> </code><code>hdfs, xxxxxxx, </code><code>0</code><code>, job_201403261945_0002, xxxxx/jobToken, xxxx/job.xml, /usr/local/jdk1.</code><code>6</code><code>.0_37/jre/bin/java,</code>

<code> </code><code>-classpath,xxxxxx.jar, -Dhadoop.log.dir=/home/test/logs/hadoop/mapred, -Dhadoop.root.logger=INFO,console,</code>

<code> </code><code>org.apache.hadoop.mapred.JobLocalizer, hdfs, job_201403261945_0002, localhost.localdomain, </code><code>57536</code><code>]</code>

其中比較重要的是taskControllerExe 這個參數,它代表了taskcontroller的可執行檔案(本例中是/home/test/platform/hadoop-2.0.0-mr1-cdh4.2.0/bin/../sbin/Linux-amd64-64/task-controller)

而execute方法其實最終調用了task-controller.

task-controller的源碼在 src/c++/task-controller目錄下。

在configuration.c中定義了對目錄屬主進行檢查:

<code>static</code> <code>int</code> <code>is_only_root_writable(</code><code>const</code> <code>char</code> <code>*file) {</code>

<code>.......</code>

<code>  </code><code>if</code> <code>(file_stat.st_uid != 0) {</code>

<code>    </code><code>fprintf</code><code>(LOGFILE, </code><code>"File %s must be owned by root, but is owned by %d\n"</code><code>,</code>

<code>            </code><code>file, file_stat.st_uid);</code>

<code>    </code><code>return</code> <code>0;</code>

如果檢查的檔案屬主不是root,則報錯。

調用這個方法的代碼:

<code>int</code> <code>check_configuration_permissions(</code><code>const</code> <code>char</code><code>* file_name) {</code>

<code>  </code><code>// copy the input so that we can modify it with dirname</code>

<code>  </code><code>char</code><code>* dir = strdup(file_name);</code>

<code>  </code><code>char</code><code>* buffer = dir;</code>

<code>  </code><code>do</code> <code>{</code>

<code>    </code><code>if</code> <code>(!is_only_root_writable(dir)) {</code>

<code>      </code><code>free</code><code>(buffer);</code>

<code>      </code><code>return</code> <code>-1;</code>

<code>    </code><code>dir = dirname(dir);</code>

<code>  </code><code>} </code><code>while</code> <code>(</code><code>strcmp</code><code>(dir, </code><code>"/"</code><code>) != 0);</code>

<code>  </code><code>free</code><code>(buffer);</code>

<code>  </code><code>return</code> <code>0;</code>

即check_configuration_permissions會調用is_only_root_writable方法對二進制檔案所在目錄向上遞歸做父目錄屬主的檢查,如果有一個目錄屬主不為root,就會出錯。這就要求整個chain上的目錄屬主都需要是root.

這其實是出于taskcontroller的安全考慮,在代碼中定義了不少關于這個可執行檔案的權限的驗證,隻要有一個地方設定不正确,tasktracker都不會正常運作。

cloudra官方文檔對這個檔案的權限描述如下:

<code>The Task-controller program is used to allow the TaskTracker to run tasks under the Unix account of the user </code><code>who</code> <code>submitted the job </code><code>in</code> <code>the first place.</code>

<code>It is a setuid binary that must have a very specific </code><code>set</code> <code>of permissions and ownership </code><code>in</code> <code>order to </code><code>function</code> <code>correctly. In particular, it must:</code>

<code>    </code><code>1)Be owned by root</code>

<code>    </code><code>2)Be owned by a group that contains only the user running the MapReduce daemons</code>

<code>    </code><code>3)Be setuid</code>

<code>    </code><code>4)Be group readable and executable</code>

問題還沒有結束,taskcontroller有一個配置檔案為taskcontroller.cfg.關于這個配置檔案位置的擷取比較讓人糾結。

搜到有些文檔說是通過設定HADOOP_SECURITY_CONF_DIR即可,但是在cdh4.2.0中,這個環境變量并不會生效,可以通過打patch來解決:

<a href="https://issues.apache.org/jira/browse/MAPREDUCE-4397" target="_blank">https://issues.apache.org/jira/browse/MAPREDUCE-4397</a>

預設情況下,目錄取值的方法如下:

<code>#ifndef HADOOP_CONF_DIR    //如果編譯時不指定HADOOP_CONF_DIR的值,沒調用infer_conf_dir方法。</code>

<code>  </code><code>conf_dir = infer_conf_dir(argv[0]);</code>

<code>  </code><code>if</code> <code>(conf_dir == NULL) {</code>

<code>    </code><code>fprintf</code><code>(LOGFILE, </code><code>"Couldn't infer HADOOP_CONF_DIR. Please set in environment\n"</code><code>);</code>

<code>    </code><code>return</code> <code>INVALID_CONFIG_FILE;</code>

<code>#else</code>

<code>  </code><code>conf_dir = strdup(STRINGIFY(HADOOP_CONF_DIR));</code>

<code>#endif</code>

其中infer_conf_dir方法如下,即通過擷取二進制檔案的相對路徑來得到配置檔案的存放目錄,比如我們線上執行檔案的位置為/home/test/platform/hadoop-2.0.0-mr1-cdh4.2.0/bin/../sbin/Linux-amd64-64/task-controller,配置檔案的位置為

/home/test/platform/hadoop-2.0.0-mr1-cdh4.2.0/conf/taskcontroller.cfg:

<code>char</code> <code>*infer_conf_dir(</code><code>char</code> <code>*executable_file) {</code>

<code>  </code><code>char</code> <code>*result;</code>

<code>  </code><code>char</code> <code>*exec_dup = strdup(executable_file);</code>

<code>  </code><code>char</code> <code>*dir = dirname(exec_dup);</code>

<code>  </code><code>int</code> <code>relative_len = </code><code>strlen</code><code>(dir) + 1 + </code><code>strlen</code><code>(CONF_DIR_RELATIVE_TO_EXEC) + 1;</code>

<code>  </code><code>char</code> <code>*relative_unresolved = </code><code>malloc</code><code>(relative_len);</code>

<code>  </code><code>snprintf(relative_unresolved, relative_len, </code><code>"%s/%s"</code><code>,</code>

<code>           </code><code>dir, CONF_DIR_RELATIVE_TO_EXEC);</code>

<code>  </code><code>result = realpath(relative_unresolved, NULL);</code>

<code>  </code><code>// realpath will return NULL if the directory doesn't exist</code>

關于taskcontrol相關類的實作放在後面的檔案講解。

本文轉自菜菜光 51CTO部落格,原文連結:http://blog.51cto.com/caiguangguang/1385587,如需轉載請自行聯系原作者